From 9a0feb3eb0b91d66211f23e2a496410026c1d28d Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 09:15:08 +0200 Subject: [PATCH 01/13] Adds basic "loading topologies" indicator for initial load. Which can take a while sometimes. --- client/app/scripts/charts/nodes-error.js | 13 ++++---- .../app/scripts/components/debug-toolbar.js | 30 ++++++++++++++++++- client/app/scripts/components/nodes.js | 16 ++++++++-- client/app/scripts/components/status.js | 7 +---- client/app/scripts/constants/action-types.js | 1 + client/app/scripts/reducers/root.js | 4 +++ client/app/styles/main.less | 8 ++++- 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/client/app/scripts/charts/nodes-error.js b/client/app/scripts/charts/nodes-error.js index 877fa2517..ba5cfbb6b 100644 --- a/client/app/scripts/charts/nodes-error.js +++ b/client/app/scripts/charts/nodes-error.js @@ -1,14 +1,15 @@ import React from 'react'; +import classnames from 'classnames'; -export default function NodesError({children, faIconClass, hidden}) { - let classNames = 'nodes-chart-error'; - if (hidden) { - classNames += ' hide'; - } +export default function NodesError({children, faIconClass, hidden, + mainClassName = 'nodes-chart-error'}) { + const className = classnames(mainClassName, { + hide: hidden + }); const iconClassName = `fa ${faIconClass}`; return ( -
+
diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js index 97145ff7c..ae61fff59 100644 --- a/client/app/scripts/components/debug-toolbar.js +++ b/client/app/scripts/components/debug-toolbar.js @@ -9,6 +9,7 @@ import { fromJS } from 'immutable'; import debug from 'debug'; const log = debug('scope:debug-panel'); +import ActionTypes from '../constants/action-types'; import { receiveNodesDelta } from '../actions/app-actions'; import { getNodeColor, getNodeColorDark, text2degree } from '../utils/color-utils'; @@ -111,11 +112,13 @@ function stopPerf() { Perf.printWasted(measurements); } + function startPerf(delay) { Perf.start(); setTimeout(stopPerf, delay * 1000); } + export function showingDebugToolbar() { return (('debugToolbar' in localStorage && JSON.parse(localStorage.debugToolbar)) || location.pathname.indexOf('debug') > -1); @@ -134,11 +137,23 @@ function enableLog(ns) { window.location.reload(); } + function disableLog() { debug.disable(); window.location.reload(); } + +function setAppState(fn) { + return (dispatch) => { + dispatch({ + type: ActionTypes.DEBUG_TOOLBAR_INTERFERING, + fn + }); + }; +} + + class DebugToolbar extends React.Component { constructor(props, context) { @@ -162,6 +177,10 @@ class DebugToolbar extends React.Component { }); } + setLoading(loading) { + this.props.setAppState(state => state.set('topologiesLoaded', !loading)); + } + addNodes(n, prefix = 'zing') { const ns = this.props.nodes; const nodeNames = ns.keySeq().toJS(); @@ -243,6 +262,12 @@ class DebugToolbar extends React.Component { ))} +
+ + + +
+
@@ -254,6 +279,7 @@ class DebugToolbar extends React.Component { } } + function mapStateToProps(state) { return { nodes: state.get('nodes'), @@ -261,6 +287,8 @@ function mapStateToProps(state) { }; } + export default connect( - mapStateToProps + mapStateToProps, + {setAppState} )(DebugToolbar); diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index d2ce010be..9da9992b6 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -62,15 +62,24 @@ class Nodes extends React.Component { ); } + renderLoading(show) { + return ( + + ); + } + render() { - const { nodes, selectedNodeId, topologyEmpty } = this.props; + const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); - const errorEmpty = this.renderEmptyTopologyError(topologyEmpty); return (
- {topologyEmpty && errorEmpty} + {!topologiesLoaded ? + this.renderLoading(!topologiesLoaded) : + (topologyEmpty && this.renderEmptyTopologyError(topologyEmpty))} Date: Thu, 12 May 2016 11:43:50 +0200 Subject: [PATCH 02/13] Adds a loading indicator for the initial node-load - Sometimes can take a second to get the initial nodes - Not doing the transition between topos atm as its a bit distracting popping up and down. --- client/app/scripts/charts/nodes-error.js | 6 +++-- client/app/scripts/components/nodes.js | 32 ++++++++++++++++++------ client/app/scripts/components/status.js | 6 ++++- client/app/scripts/reducers/root.js | 4 ++- client/app/styles/main.less | 5 +++- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/client/app/scripts/charts/nodes-error.js b/client/app/scripts/charts/nodes-error.js index ba5cfbb6b..e6204bfb1 100644 --- a/client/app/scripts/charts/nodes-error.js +++ b/client/app/scripts/charts/nodes-error.js @@ -10,8 +10,10 @@ export default function NodesError({children, faIconClass, hidden, return (
-
- +
+
+ +
{children}
diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 9da9992b6..8b8c510eb 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import NodesChart from '../charts/nodes-chart'; import NodesError from '../charts/nodes-error'; -import { isTopologyEmpty } from '../utils/topology-utils'; +import { findTopologyById, isTopologyEmpty } from '../utils/topology-utils'; const navbarHeight = 160; const marginTop = 0; @@ -27,6 +27,17 @@ function getLayoutPrecision(nodesCount) { return precision; } +function getNodeType(topology, topologies) { + if (!topology || topologies.size === 0) { + return ''; + } + if (topology.get('parentId')) { + const parentTopology = findTopologyById(topologies, topology.get('parentId')); + return parentTopology.get('name'); + } + return topology.get('name'); +} + class Nodes extends React.Component { constructor(props, context) { super(props, context); @@ -62,24 +73,26 @@ class Nodes extends React.Component { ); } - renderLoading(show) { + renderLoading(message, show) { return ( ); } render() { - const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded } = this.props; + const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded, nodesLoaded, topologies, + topology } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); return (
- {!topologiesLoaded ? - this.renderLoading(!topologiesLoaded) : - (topologyEmpty && this.renderEmptyTopologyError(topologyEmpty))} + {this.renderLoading('Loading topologies...', !topologiesLoaded)} + {this.renderLoading(`Loading ${getNodeType(topology, topologies)}...`, + topologiesLoaded && !nodesLoaded)} + {this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)} details nodes: makeOrderedMap(), // nodeId -> node + nodesLoaded: false, // nodes cache, infrequently updated, used for search nodesByTopology: makeMap(), // topologyId -> nodes pinnedMetric: null, @@ -61,7 +62,7 @@ export const initialState = makeMap({ updatePausedAt: null, // Date version: '...', versionUpdate: null, - websocketClosed: true, + websocketClosed: false, exportingGraph: false }); @@ -483,6 +484,7 @@ export function rootReducer(state = initialState, action) { case ActionTypes.RECEIVE_NODES_DELTA: { const emptyMessage = !action.delta.add && !action.delta.remove && !action.delta.update; + state = state.set('nodesLoaded', true); if (!emptyMessage) { log('RECEIVE_NODES_DELTA', diff --git a/client/app/styles/main.less b/client/app/styles/main.less index 72eb7511e..934743677 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -317,8 +317,11 @@ h2 { } } + &-loading &-error-icon-container { + animation: blinking 2.0s infinite @base-ease; + } + &-loading { - animation: blinking 2s infinite @base-ease; text-align: center; } From 7dfae47c851720d8f41e0d4267a9668e44772f58 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 11:54:35 +0200 Subject: [PATCH 03/13] No title case on the loading messages --- client/app/scripts/components/nodes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 8b8c510eb..9f18b3828 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -31,11 +31,12 @@ function getNodeType(topology, topologies) { if (!topology || topologies.size === 0) { return ''; } + let name = topology.get('name'); if (topology.get('parentId')) { const parentTopology = findTopologyById(topologies, topology.get('parentId')); - return parentTopology.get('name'); + name = parentTopology.get('name'); } - return topology.get('name'); + return name.toLowerCase(); } class Nodes extends React.Component { From 5df5e402f3df926d6a8146e43e658402689ad98f Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 12:50:40 +0200 Subject: [PATCH 04/13] Avoid blurry text in topo loading message --- client/app/styles/main.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/app/styles/main.less b/client/app/styles/main.less index 934743677..f1c19adce 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -301,7 +301,8 @@ h2 { position: absolute; left: 50%; top: 50%; - transform: translate(-50%, -50%); + margin-left: -16.5%; + margin-top: -275px; color: @text-secondary-color; width: 33%; height: 550px; From 04f4071fd3be298ef83c47633762591ed522ac02 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 13:00:38 +0200 Subject: [PATCH 05/13] Handle loading message for empty topos correctly. It was showing infinite loading... --- client/app/scripts/actions/app-actions.js | 2 ++ client/app/scripts/constants/action-types.js | 1 + client/app/scripts/reducers/root.js | 16 ++++------------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 5704ccdd8..8ae665651 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -468,6 +468,8 @@ export function receiveNodeDetails(details) { export function receiveNodesDelta(delta) { return (dispatch, getState) => { + dispatch({ type: ActionTypes.SET_RECEIVED_NODES_DELTA }); + if (delta.add || delta.update || delta.remove) { const state = getState(); if (state.get('updatePausedAt') !== null) { diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index 9a4c0e44d..72d42793d 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -54,6 +54,7 @@ const ACTION_TYPES = [ 'PIN_NETWORK', 'UNPIN_NETWORK', 'SHOW_NETWORKS', + 'SET_RECEIVED_NODES_DELTA', ]; export default _.zipObject(ACTION_TYPES, ACTION_TYPES); diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index bc7c304ad..21f7d572a 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -10,7 +10,6 @@ import { getNetworkNodes, getAvailableNetworks } from '../utils/network-view-uti import { findTopologyById, getAdjacentNodes, setTopologyUrlsById, updateTopologyIds, filterHiddenTopologies } from '../utils/topology-utils'; -const log = debug('scope:app-store'); const error = debug('scope:error'); // Helpers @@ -481,18 +480,11 @@ export function rootReducer(state = initialState, action) { return state; } + case ActionTypes.SET_RECEIVED_NODES_DELTA: { + return state.set('nodesLoaded', true); + } + case ActionTypes.RECEIVE_NODES_DELTA: { - const emptyMessage = !action.delta.add && !action.delta.remove - && !action.delta.update; - state = state.set('nodesLoaded', true); - - if (!emptyMessage) { - log('RECEIVE_NODES_DELTA', - 'remove', _.size(action.delta.remove), - 'update', _.size(action.delta.update), - 'add', _.size(action.delta.add)); - } - state = state.set('errorUrl', null); // nodes that no longer exist From 335cc1154a0f09e68e409417f2c40921690314cc Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 13:25:03 +0200 Subject: [PATCH 06/13] Smooth fade-out of node-loading message --- client/app/scripts/actions/app-actions.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 8ae665651..534391783 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -468,7 +468,12 @@ export function receiveNodeDetails(details) { export function receiveNodesDelta(delta) { return (dispatch, getState) => { - dispatch({ type: ActionTypes.SET_RECEIVED_NODES_DELTA }); + // + // allow css-animation to run smoothly by scheduling it to run on the + // next tick after any potentially expensive canvas re-draws have been + // completed. + // + setTimeout(() => dispatch({ type: ActionTypes.SET_RECEIVED_NODES_DELTA }), 0); if (delta.add || delta.update || delta.remove) { const state = getState(); From b92a990b2e709cada53c0d6320efb3b5b4bc559b Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 13:31:43 +0200 Subject: [PATCH 07/13] Restore log error message --- client/app/scripts/reducers/root.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index 21f7d572a..893247774 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -10,6 +10,7 @@ import { getNetworkNodes, getAvailableNetworks } from '../utils/network-view-uti import { findTopologyById, getAdjacentNodes, setTopologyUrlsById, updateTopologyIds, filterHiddenTopologies } from '../utils/topology-utils'; +const log = debug('scope:app-store'); const error = debug('scope:error'); // Helpers @@ -485,6 +486,16 @@ export function rootReducer(state = initialState, action) { } case ActionTypes.RECEIVE_NODES_DELTA: { + const emptyMessage = !action.delta.add && !action.delta.remove + && !action.delta.update; + + if (!emptyMessage) { + log('RECEIVE_NODES_DELTA', + 'remove', _.size(action.delta.remove), + 'update', _.size(action.delta.update), + 'add', _.size(action.delta.add)); + } + state = state.set('errorUrl', null); // nodes that no longer exist From 74e0540a005a42c01c3efbbd5ac9888bb98314d8 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 14:08:50 +0200 Subject: [PATCH 08/13] More personal loading messages --- client/app/scripts/components/nodes.js | 27 ++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 9f18b3828..3186cef00 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -1,4 +1,5 @@ import React from 'react'; +import _ from 'lodash'; import { connect } from 'react-redux'; import NodesChart from '../charts/nodes-chart'; @@ -9,6 +10,19 @@ const navbarHeight = 160; const marginTop = 0; const detailsWidth = 450; + +const APOLOGIES = [ + "We're terribly sorry, the THINGS will be ready in a second!", + "Forgive us! We're just loading the THINGS...", + 'A thousand apologies! Here come the THINGS!... any second now...' +]; + + +function apologizeFor(nodeType, template) { + return template.replace('THINGS', nodeType); +} + + /** * dynamic coords precision based on topology size */ @@ -44,9 +58,12 @@ class Nodes extends React.Component { super(props, context); this.handleResize = this.handleResize.bind(this); + const [topologiesLoadingTemplate, nodeLoadingTemplate] = _.sampleSize(APOLOGIES, 2); this.state = { width: window.innerWidth, - height: window.innerHeight - navbarHeight - marginTop + height: window.innerHeight - navbarHeight - marginTop, + topologiesLoadingTemplate, + nodeLoadingTemplate, }; } @@ -87,12 +104,14 @@ class Nodes extends React.Component { topology } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); + const topologyLoadingMessage = apologizeFor('toplogies', this.state.topologiesLoadingTemplate); + const nodeLoadingMessage = apologizeFor(getNodeType(topology, topologies), + this.state.nodeLoadingTemplate); return (
- {this.renderLoading('Loading topologies...', !topologiesLoaded)} - {this.renderLoading(`Loading ${getNodeType(topology, topologies)}...`, - topologiesLoaded && !nodesLoaded)} + {this.renderLoading(topologyLoadingMessage, !topologiesLoaded)} + {this.renderLoading(nodeLoadingMessage, topologiesLoaded && !nodesLoaded)} {this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)} Date: Thu, 12 May 2016 14:14:58 +0200 Subject: [PATCH 09/13] Less apologetic ui messages --- client/app/scripts/components/nodes.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 3186cef00..abcba83ba 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -11,14 +11,19 @@ const marginTop = 0; const detailsWidth = 450; -const APOLOGIES = [ - "We're terribly sorry, the THINGS will be ready in a second!", - "Forgive us! We're just loading the THINGS...", - 'A thousand apologies! Here come the THINGS!... any second now...' +const LOADING_TEMPLATES = [ + 'Just loading the THINGS... any second now...', + "Loading the THINGS. They'll be here in a jiffy...", + 'Crunching the THINGS', + 'Deleting all the THINGS', + 'rm -rf *THINGS*', + 'Waiting for all the THINGS', + 'Containing the THINGS', + 'Processing the THINGS', ]; -function apologizeFor(nodeType, template) { +function renderTemplate(nodeType, template) { return template.replace('THINGS', nodeType); } @@ -58,7 +63,7 @@ class Nodes extends React.Component { super(props, context); this.handleResize = this.handleResize.bind(this); - const [topologiesLoadingTemplate, nodeLoadingTemplate] = _.sampleSize(APOLOGIES, 2); + const [topologiesLoadingTemplate, nodeLoadingTemplate] = _.sampleSize(LOADING_TEMPLATES, 2); this.state = { width: window.innerWidth, height: window.innerHeight - navbarHeight - marginTop, @@ -104,8 +109,9 @@ class Nodes extends React.Component { topology } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); - const topologyLoadingMessage = apologizeFor('toplogies', this.state.topologiesLoadingTemplate); - const nodeLoadingMessage = apologizeFor(getNodeType(topology, topologies), + const topologyLoadingMessage = renderTemplate('toplogies', + this.state.topologiesLoadingTemplate); + const nodeLoadingMessage = renderTemplate(getNodeType(topology, topologies), this.state.nodeLoadingTemplate); return ( From 50a1c11eaaf2fd01f9249c48a5596f83df8cb370 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 14:23:47 +0200 Subject: [PATCH 10/13] New and improved loading messages. Are these getting better or worse... --- client/app/scripts/components/nodes.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index abcba83ba..ea26802aa 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -12,14 +12,17 @@ const detailsWidth = 450; const LOADING_TEMPLATES = [ - 'Just loading the THINGS... any second now...', - "Loading the THINGS. They'll be here in a jiffy...", - 'Crunching the THINGS', - 'Deleting all the THINGS', - 'rm -rf *THINGS*', - 'Waiting for all the THINGS', - 'Containing the THINGS', - 'Processing the THINGS', + 'Loading THINGS', + 'Verifying THINGS', + 'Fetching THINGS', + 'Processing THINGS', + 'Reticulating THINGS', + 'Decompressing THINGS', + 'Compressing THINGS', + 'Locating THINGS', + 'Optimizing THINGS', + 'Transporting THINGS', + 'Double checking THINGS', ]; From fba40de231ba445d2b98aae52490ce47ebc894bb Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 12 May 2016 14:56:02 +0200 Subject: [PATCH 11/13] Typo and removing some more mutatey sounding verbs --- client/app/scripts/components/nodes.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index ea26802aa..355629411 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -17,8 +17,6 @@ const LOADING_TEMPLATES = [ 'Fetching THINGS', 'Processing THINGS', 'Reticulating THINGS', - 'Decompressing THINGS', - 'Compressing THINGS', 'Locating THINGS', 'Optimizing THINGS', 'Transporting THINGS', @@ -112,7 +110,7 @@ class Nodes extends React.Component { topology } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); - const topologyLoadingMessage = renderTemplate('toplogies', + const topologyLoadingMessage = renderTemplate('topologies', this.state.topologiesLoadingTemplate); const nodeLoadingMessage = renderTemplate(getNodeType(topology, topologies), this.state.nodeLoadingTemplate); From 6e3bec8c7098d6e955f04b1c298e44b030263fde Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 28 Jul 2016 17:59:48 +0200 Subject: [PATCH 12/13] Pulls loading out into new cmp, adds delayed show for loading --- client/app/scripts/components/loading.js | 59 ++++++++++++++++++++++++ client/app/scripts/components/nodes.js | 58 ++++------------------- client/app/scripts/utils/delayed-show.js | 55 ++++++++++++++++++++++ 3 files changed, 123 insertions(+), 49 deletions(-) create mode 100644 client/app/scripts/components/loading.js create mode 100644 client/app/scripts/utils/delayed-show.js diff --git a/client/app/scripts/components/loading.js b/client/app/scripts/components/loading.js new file mode 100644 index 000000000..afd21b940 --- /dev/null +++ b/client/app/scripts/components/loading.js @@ -0,0 +1,59 @@ +import React from 'react'; +import _ from 'lodash'; + +import { findTopologyById } from '../utils/topology-utils'; +import NodesError from '../charts/nodes-error'; + + +const LOADING_TEMPLATES = [ + 'Loading THINGS', + 'Verifying THINGS', + 'Fetching THINGS', + 'Processing THINGS', + 'Reticulating THINGS', + 'Locating THINGS', + 'Optimizing THINGS', + 'Transporting THINGS', + 'Double checking THINGS', +]; + + +export function getNodeType(topology, topologies) { + if (!topology || topologies.size === 0) { + return ''; + } + let name = topology.get('name'); + if (topology.get('parentId')) { + const parentTopology = findTopologyById(topologies, topology.get('parentId')); + name = parentTopology.get('name'); + } + return name.toLowerCase(); +} + + +function renderTemplate(nodeType, template) { + return template.replace('THINGS', nodeType); +} + + +export class Loading extends React.Component { + + constructor(props, context) { + super(props, context); + + this.state = { + template: _.sample(LOADING_TEMPLATES) + }; + } + + render() { + const { itemType, show } = this.props; + const message = renderTemplate(itemType, this.state.template); + return ( + + ); + } + +} diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 355629411..92db420dd 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -1,34 +1,17 @@ import React from 'react'; -import _ from 'lodash'; import { connect } from 'react-redux'; import NodesChart from '../charts/nodes-chart'; import NodesError from '../charts/nodes-error'; -import { findTopologyById, isTopologyEmpty } from '../utils/topology-utils'; +import { DelayedShow } from '../utils/delayed-show'; +import { Loading, getNodeType } from './loading'; +import { isTopologyEmpty } from '../utils/topology-utils'; const navbarHeight = 160; const marginTop = 0; const detailsWidth = 450; -const LOADING_TEMPLATES = [ - 'Loading THINGS', - 'Verifying THINGS', - 'Fetching THINGS', - 'Processing THINGS', - 'Reticulating THINGS', - 'Locating THINGS', - 'Optimizing THINGS', - 'Transporting THINGS', - 'Double checking THINGS', -]; - - -function renderTemplate(nodeType, template) { - return template.replace('THINGS', nodeType); -} - - /** * dynamic coords precision based on topology size */ @@ -47,29 +30,14 @@ function getLayoutPrecision(nodesCount) { return precision; } -function getNodeType(topology, topologies) { - if (!topology || topologies.size === 0) { - return ''; - } - let name = topology.get('name'); - if (topology.get('parentId')) { - const parentTopology = findTopologyById(topologies, topology.get('parentId')); - name = parentTopology.get('name'); - } - return name.toLowerCase(); -} - class Nodes extends React.Component { constructor(props, context) { super(props, context); this.handleResize = this.handleResize.bind(this); - const [topologiesLoadingTemplate, nodeLoadingTemplate] = _.sampleSize(LOADING_TEMPLATES, 2); this.state = { width: window.innerWidth, height: window.innerHeight - navbarHeight - marginTop, - topologiesLoadingTemplate, - nodeLoadingTemplate, }; } @@ -97,28 +65,20 @@ class Nodes extends React.Component { ); } - renderLoading(message, show) { - return ( - - ); - } - render() { const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded, nodesLoaded, topologies, topology } = this.props; const layoutPrecision = getLayoutPrecision(nodes.size); const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId); - const topologyLoadingMessage = renderTemplate('topologies', - this.state.topologiesLoadingTemplate); - const nodeLoadingMessage = renderTemplate(getNodeType(topology, topologies), - this.state.nodeLoadingTemplate); return (
- {this.renderLoading(topologyLoadingMessage, !topologiesLoaded)} - {this.renderLoading(nodeLoadingMessage, topologiesLoaded && !nodesLoaded)} + + + + {this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)} this.setState({ show: true }), this.props.delay); + } + + cancelShow() { + clearTimeout(this.showTimeout); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.show === this.props.show) { + return; + } + + if (nextProps.show) { + this.scheduleShow(); + } else { + this.cancelShow(); + this.setState({ show: false }); + } + } + + render() { + const { children } = this.props; + const { show } = this.state; + const style = { + opacity: show ? 1 : 0, + transition: 'opacity 0.5s ease-in-out', + }; + return ( +
+ {children} +
+ ); + } +} + + +DelayedShow.defaultProps = { + delay: 1000 +}; From 19f08ee81ffa71519c40336ff94863bfcbdaf7f1 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Tue, 2 Aug 2016 22:09:08 +0200 Subject: [PATCH 13/13] Review feedback on loading-indicator --- client/app/scripts/components/loading.js | 1 - client/app/scripts/components/nodes.js | 2 +- client/app/scripts/utils/delayed-show.js | 20 +++++++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/client/app/scripts/components/loading.js b/client/app/scripts/components/loading.js index afd21b940..2696f46b1 100644 --- a/client/app/scripts/components/loading.js +++ b/client/app/scripts/components/loading.js @@ -14,7 +14,6 @@ const LOADING_TEMPLATES = [ 'Locating THINGS', 'Optimizing THINGS', 'Transporting THINGS', - 'Double checking THINGS', ]; diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 92db420dd..38ae981c2 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -73,7 +73,7 @@ class Nodes extends React.Component { return (
- + this.setState({ show: true }), this.props.delay); - } - - cancelShow() { - clearTimeout(this.showTimeout); + componentWillUnmount() { + this.cancelShow(); } componentWillReceiveProps(nextProps) { @@ -34,6 +32,14 @@ export class DelayedShow extends React.Component { } } + scheduleShow() { + this.showTimeout = setTimeout(() => this.setState({ show: true }), this.props.delay); + } + + cancelShow() { + clearTimeout(this.showTimeout); + } + render() { const { children } = this.props; const { show } = this.state;