diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index 300c4bbfe..d6212f200 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -643,6 +643,17 @@ export function receiveNotFound(nodeId) {
};
}
+export function toggleContrastMode(enabled) {
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.TOGGLE_CONTRAST_MODE,
+ enabled,
+ });
+
+ updateRoute(getState);
+ };
+}
+
export function route(urlState) {
return (dispatch, getState) => {
dispatch({
@@ -664,6 +675,10 @@ export function route(urlState) {
state.get('nodeDetails'),
dispatch
);
+
+ if (urlState.contrastMode) {
+ dispatch(toggleContrastMode(true));
+ }
};
}
diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js
index 1deab2f04..f443a1953 100644
--- a/client/app/scripts/charts/edge.js
+++ b/client/app/scripts/charts/edge.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import classNames from 'classnames';
import { enterEdge, leaveEdge } from '../actions/app-actions';
-import { isContrastMode } from '../utils/contrast-utils';
import { NODE_BASE_SIZE } from '../constants/styles';
class Edge extends React.Component {
@@ -15,9 +14,9 @@ class Edge extends React.Component {
}
render() {
- const { id, path, highlighted, blurred, focused, scale } = this.props;
+ const { id, path, highlighted, blurred, focused, scale, contrastMode } = this.props;
const className = classNames('edge', { highlighted, blurred, focused });
- const thickness = scale * (isContrastMode() ? 0.02 : 0.01) * NODE_BASE_SIZE;
+ const thickness = scale * (contrastMode ? 0.02 : 0.01) * NODE_BASE_SIZE;
// Draws the edge so that its thickness reflects the zoom scale.
// Edge shadow is always made 10x thicker than the edge itself.
@@ -41,7 +40,13 @@ class Edge extends React.Component {
}
}
+function mapStateToProps(state) {
+ return {
+ contrastMode: state.get('contrastMode')
+ };
+}
+
export default connect(
- null,
+ mapStateToProps,
{ enterEdge, leaveEdge }
)(Edge);
diff --git a/client/app/scripts/charts/node-networks-overlay.js b/client/app/scripts/charts/node-networks-overlay.js
index ae1b45710..0522a83e0 100644
--- a/client/app/scripts/charts/node-networks-overlay.js
+++ b/client/app/scripts/charts/node-networks-overlay.js
@@ -1,8 +1,9 @@
import React from 'react';
import { scaleBand } from 'd3-scale';
import { List as makeList } from 'immutable';
+import { connect } from 'react-redux';
+
import { getNetworkColor } from '../utils/color-utils';
-import { isContrastMode } from '../utils/contrast-utils';
import { NODE_BASE_SIZE } from '../constants/styles';
// Min size is about a quarter of the width, feels about right.
@@ -13,7 +14,7 @@ const borderRadius = 0.01;
const offset = 0.67;
const x = scaleBand();
-function NodeNetworksOverlay({ stack, networks = makeList() }) {
+function NodeNetworksOverlay({ stack, networks = makeList(), contrastMode }) {
const barWidth = Math.max(1, minBarWidth * networks.size);
const yPosition = offset - (barHeight * 0.5);
@@ -37,7 +38,7 @@ function NodeNetworksOverlay({ stack, networks = makeList() }) {
/>
));
- const translateY = stack && isContrastMode() ? 0.15 : 0;
+ const translateY = stack && contrastMode ? 0.15 : 0;
return (
{bars.toJS()}
@@ -45,4 +46,10 @@ function NodeNetworksOverlay({ stack, networks = makeList() }) {
);
}
-export default NodeNetworksOverlay;
+function mapStateToProps(state) {
+ return {
+ contrastMode: state.get('contrastMode')
+ };
+}
+
+export default connect(mapStateToProps)(NodeNetworksOverlay);
diff --git a/client/app/scripts/charts/node-shape-stack.js b/client/app/scripts/charts/node-shape-stack.js
index 268ab1c04..330a948cb 100644
--- a/client/app/scripts/charts/node-shape-stack.js
+++ b/client/app/scripts/charts/node-shape-stack.js
@@ -1,10 +1,10 @@
import React from 'react';
+import { connect } from 'react-redux';
import { NODE_BASE_SIZE } from '../constants/styles';
-import { isContrastMode } from '../utils/contrast-utils';
-export default function NodeShapeStack(props) {
- const shift = isContrastMode() ? 0.15 : 0.1;
+function NodeShapeStack(props) {
+ const shift = props.contrastMode ? 0.15 : 0.1;
const highlightScale = [1, 1 + shift];
const dy = NODE_BASE_SIZE * shift;
@@ -26,3 +26,11 @@ export default function NodeShapeStack(props) {
);
}
+
+function mapStateToProps(state) {
+ return {
+ contrastMode: state.get('contrastMode')
+ };
+}
+
+export default connect(mapStateToProps)(NodeShapeStack);
diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index 05ac9bf59..73f3dce47 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -13,7 +13,7 @@ import Topologies from './topologies';
import TopologyOptions from './topology-options';
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric,
- selectMetric, toggleHelp, toggleGridMode } from '../actions/app-actions';
+ selectMetric, toggleHelp, toggleGridMode, toggleContrastMode } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
import GridModeSelector from './grid-mode-selector';
@@ -153,11 +153,12 @@ function mapStateToProps(state) {
showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
showingNetworkSelector: state.get('availableNetworks').count() > 0,
showingTerminal: state.get('controlPipes').size > 0,
- urlState: getUrlState(state)
+ urlState: getUrlState(state),
+ contrastMode: state.get('contrastMode')
};
}
-
export default connect(
- mapStateToProps
+ mapStateToProps,
+ dispatch => ({ dispatch, toggleContrastMode })
)(App);
diff --git a/client/app/scripts/components/footer.js b/client/app/scripts/components/footer.js
index 3633ece00..a9c8bdf05 100644
--- a/client/app/scripts/components/footer.js
+++ b/client/app/scripts/components/footer.js
@@ -4,19 +4,22 @@ import moment from 'moment';
import Plugins from './plugins';
import { getUpdateBufferSize } from '../utils/update-buffer-utils';
-import { contrastModeUrl, isContrastMode } from '../utils/contrast-utils';
import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
- clickResumeUpdate, toggleHelp, toggleTroubleshootingMenu } from '../actions/app-actions';
-import { basePathSlash } from '../utils/web-api-utils';
+ clickResumeUpdate, toggleHelp, toggleTroubleshootingMenu, toggleContrastMode } from '../actions/app-actions';
class Footer extends React.Component {
- render() {
- const { hostname, updatePausedAt, version, versionUpdate } = this.props;
- const contrastMode = isContrastMode();
+ constructor(props, context) {
+ super(props, context);
+
+ this.handleContrastClick = this.handleContrastClick.bind(this);
+ }
+ handleContrastClick(e) {
+ e.preventDefault();
+ this.props.toggleContrastMode(!this.props.contrastMode);
+ }
+ render() {
+ const { hostname, updatePausedAt, version, versionUpdate, contrastMode } = this.props;
- // link url to switch contrast with current UI state
- const otherContrastModeUrl = contrastMode
- ? basePathSlash(window.location.pathname) : contrastModeUrl;
const otherContrastModeTitle = contrastMode
? 'Switch to normal contrast' : 'Switch to high contrast';
const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, '
@@ -76,7 +79,7 @@ class Footer extends React.Component {
title={forceRelayoutTitle}>
-
+
-
-
- ), document.getElementById('app'));
-}
-
-renderApp();
-if (module.hot) {
- module.hot.accept('./components/app', renderApp);
-}
diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js
index 81d686b8b..5913dfcca 100644
--- a/client/app/scripts/reducers/root.js
+++ b/client/app/scripts/reducers/root.js
@@ -1,3 +1,4 @@
+/* eslint-disable import/no-webpack-loader-syntax, import/no-unresolved */
import debug from 'debug';
import { size, each, includes } from 'lodash';
import { fromJS, is as isDeepEqual, List as makeList, Map as makeMap,
@@ -30,6 +31,7 @@ const topologySorter = topology => topology.get('rank');
export const initialState = makeMap({
availableCanvasMetrics: makeList(),
availableNetworks: makeList(),
+ contrastMode: false,
controlPipes: makeOrderedMap(), // pipeId -> controlPipe
controlStatus: makeMap(),
currentTopology: null,
@@ -721,6 +723,33 @@ export function rootReducer(state = initialState, action) {
return state.set('showingTroubleshootingMenu', !state.get('showingTroubleshootingMenu'));
}
+ case ActionTypes.TOGGLE_CONTRAST_MODE: {
+ const modules = [
+ require.resolve('../../styles/main.scss'),
+ require.resolve('../../styles/contrast.scss')
+ ];
+ // Bust the webpack require cache to for a re-download of the stylesheets
+ modules.forEach((i) => {
+ const children = require.cache[i] ? require.cache[i].children : [];
+ children.forEach((c) => {
+ delete require.cache[c];
+ });
+ delete require.cache[i];
+ });
+
+ if (action.enabled) {
+ require.ensure([], () => {
+ require('../../styles/contrast.scss');
+ });
+ } else {
+ require.ensure([], () => {
+ require('../../styles/main.scss');
+ });
+ }
+
+ return state.set('contrastMode', action.enabled);
+ }
+
default: {
return state;
}
diff --git a/client/app/scripts/utils/contrast-utils.js b/client/app/scripts/utils/contrast-utils.js
deleted file mode 100644
index e7f4c7a46..000000000
--- a/client/app/scripts/utils/contrast-utils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const contrastModeUrl = 'contrast.html';
-
-const contrastMode = window.location.pathname.indexOf(contrastModeUrl) > -1;
-
-export function isContrastMode() {
- return contrastMode;
-}
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index ffc4d85f1..6bc7d330c 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -19,11 +19,18 @@ function encodeURL(url) {
.replace(new RegExp(SLASH, 'g'), SLASH_REPLACEMENT);
}
-function decodeURL(url) {
+export function decodeURL(url) {
return decodeURIComponent(url.replace(new RegExp(SLASH_REPLACEMENT, 'g'), SLASH))
.replace(new RegExp(PERCENT_REPLACEMENT, 'g'), PERCENT);
}
+export function parseHashState(hash = window.location.hash) {
+ const urlStateString = hash
+ .replace('#!/state/', '')
+ .replace('#!/', '') || '{}';
+ return JSON.parse(decodeURL(urlStateString));
+}
+
function shouldReplaceState(prevState, nextState) {
// Opening a new terminal while an existing one is open.
const terminalToTerminal = (prevState.controlPipe && nextState.controlPipe);
@@ -50,7 +57,8 @@ export function getUrlState(state) {
gridSortedBy: state.get('gridSortedBy'),
gridSortedDesc: state.get('gridSortedDesc'),
topologyId: state.get('currentTopologyId'),
- topologyOptions: state.get('topologyOptions').toJS() // all options
+ topologyOptions: state.get('topologyOptions').toJS(), // all options,
+ contrastMode: state.get('contrastMode')
};
if (state.get('showingNetworks')) {
@@ -67,10 +75,7 @@ export function updateRoute(getState) {
const state = getUrlState(getState());
const stateUrl = encodeURL(JSON.stringify(state));
const dispatch = false;
- const urlStateString = window.location.hash
- .replace('#!/state/', '')
- .replace('#!/', '') || '{}';
- const prevState = JSON.parse(decodeURL(urlStateString));
+ const prevState = parseHashState();
// back up state in storage as well
storageSet(STORAGE_STATE_KEY, stateUrl);
diff --git a/client/build/favicon.ico b/client/build/favicon.ico
deleted file mode 100644
index 2d15c7808..000000000
Binary files a/client/build/favicon.ico and /dev/null differ
diff --git a/client/webpack.local.config.js b/client/webpack.local.config.js
index 1994c44aa..512982651 100644
--- a/client/webpack.local.config.js
+++ b/client/webpack.local.config.js
@@ -2,7 +2,6 @@ const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
-
/**
* This is the Webpack configuration file for local development.
* It contains local-specific configuration which includes:
@@ -28,10 +27,6 @@ module.exports = {
'./app/scripts/main.dev',
'webpack-hot-middleware/client'
],
- 'contrast-app': [
- './app/scripts/contrast-main',
- 'webpack-hot-middleware/client'
- ],
'terminal-app': [
'./app/scripts/terminal-main',
'webpack-hot-middleware/client'
@@ -45,7 +40,7 @@ module.exports = {
// Used by Webpack Dev Middleware
output: {
publicPath: '',
- path: '/',
+ path: path.join(__dirname, 'build'),
filename: '[name].js'
},
@@ -56,11 +51,6 @@ module.exports = {
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]),
- new HtmlWebpackPlugin({
- chunks: ['vendors', 'contrast-app'],
- template: 'app/html/index.html',
- filename: 'contrast.html'
- }),
new HtmlWebpackPlugin({
chunks: ['vendors', 'terminal-app'],
template: 'app/html/index.html',
diff --git a/client/webpack.production.config.js b/client/webpack.production.config.js
index 5dd6c2181..a605688a6 100644
--- a/client/webpack.production.config.js
+++ b/client/webpack.production.config.js
@@ -31,7 +31,6 @@ module.exports = {
entry: {
app: './app/scripts/main',
- 'contrast-app': './app/scripts/contrast-main',
'terminal-app': './app/scripts/terminal-main',
// keep only some in here, to make vendors and app bundles roughly same size
vendors: ['babel-polyfill', 'classnames', 'immutable',
@@ -112,12 +111,6 @@ module.exports = {
}
}),
new ExtractTextPlugin('style-[name]-[chunkhash].css'),
- new HtmlWebpackPlugin({
- hash: true,
- chunks: ['vendors', 'contrast-app'],
- template: 'app/html/index.html',
- filename: 'contrast.html'
- }),
new HtmlWebpackPlugin({
hash: true,
chunks: ['vendors', 'terminal-app'],