diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index 7ac137463d..ea85045b59 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -51,7 +51,7 @@ export default class App extends React.Component {
AppStore.addListener(this.onChange);
window.addEventListener('keyup', this.onKeyPress);
- getRouter().start({hashbang: true});
+ getRouter(this.props.base).start({hashbang: true});
if (!AppStore.isRouteSet()) {
// dont request topologies when already done via router
getTopologies(AppStore.getActiveTopologyOptions());
@@ -75,6 +75,10 @@ export default class App extends React.Component {
// width of details panel blocking a view
const detailsWidth = showingDetails ? 450 : 0;
const topMargin = 100;
+ const contrastMode = this.props.base.indexOf('contrast') > -1;
+ // link url to switch contrast with current UI state
+ const otherContrastModeUrl = contrastMode ? '/' : '/contrast.html';
+ const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast';
return (
@@ -112,9 +116,12 @@ export default class App extends React.Component {
{this.state.version}
on
{this.state.hostname}
-
-
- Report an issue
+
+
+
+
+
+
diff --git a/client/app/scripts/contrast-main.js b/client/app/scripts/contrast-main.js
new file mode 100644
index 0000000000..29b3d735dc
--- /dev/null
+++ b/client/app/scripts/contrast-main.js
@@ -0,0 +1,9 @@
+require('font-awesome-webpack');
+require('../styles/contrast.less');
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './components/app.js';
+
+ReactDOM.render(, document.getElementById('app'));
diff --git a/client/app/scripts/main.js b/client/app/scripts/main.js
index cac95b50fa..10cbcc26ad 100644
--- a/client/app/scripts/main.js
+++ b/client/app/scripts/main.js
@@ -6,4 +6,4 @@ import ReactDOM from 'react-dom';
import App from './components/app.js';
-ReactDOM.render(, document.getElementById('app'));
+ReactDOM.render(, document.getElementById('app'));
diff --git a/client/app/scripts/utils/color-utils.js b/client/app/scripts/utils/color-utils.js
index 55e26359e2..f6c9d86dca 100644
--- a/client/app/scripts/utils/color-utils.js
+++ b/client/app/scripts/utils/color-utils.js
@@ -59,7 +59,9 @@ export function getNodeColorDark(text = '', secondText = '', isPseudo = false) {
let hsl = color.hsl();
// ensure darkness
- if (hsl.l > 0.7) {
+ if (hsl.h > 20 && hsl.h < 120) {
+ hsl = hsl.darker(2);
+ } else if (hsl.l > 0.7) {
hsl = hsl.darker(1.5);
} else {
hsl = hsl.darker(1);
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index 12bd691316..f55a9af2db 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -16,7 +16,9 @@ export function updateRoute() {
const state = AppStore.getAppState();
const stateUrl = JSON.stringify(state);
const dispatch = false;
- const urlStateString = window.location.hash.replace('#!/state/', '') || '{}';
+ const urlStateString = window.location.hash
+ .replace('#!/state/', '')
+ .replace('#!/', '') || '{}';
const prevState = JSON.parse(urlStateString);
if (shouldReplaceState(prevState, state)) {
@@ -36,6 +38,7 @@ page('/state/:state', function(ctx) {
route(state);
});
-export function getRouter() {
+export function getRouter(base) {
+ page.base(base);
return page;
}
diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js
index 7272ab6546..aab8056b46 100644
--- a/client/app/scripts/utils/web-api-utils.js
+++ b/client/app/scripts/utils/web-api-utils.js
@@ -8,8 +8,6 @@ import { clearControlError, closeWebsocket, openWebsocket, receiveError,
import { API_INTERVAL, TOPOLOGY_INTERVAL } from '../constants/timer';
-const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
-const wsUrl = wsProto + '://' + location.host + location.pathname.replace(/\/$/, '');
const log = debug('scope:web-api-utils');
const reconnectTimerInterval = 5000;
@@ -47,6 +45,9 @@ export function basePath(urlPath) {
return parts.join('/').replace(/\/$/, '');
}
+const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
+const wsUrl = wsProto + '://' + location.host + basePath(location.pathname);
+
function createWebsocket(topologyUrl, optionsQuery) {
if (socket) {
socket.onclose = null;
diff --git a/client/app/styles/contrast.less b/client/app/styles/contrast.less
new file mode 100644
index 0000000000..1931662bba
--- /dev/null
+++ b/client/app/styles/contrast.less
@@ -0,0 +1,31 @@
+@import "main";
+
+@background-color: lighten(@primary-color, 75%);
+@background-lighter-color: lighten(@background-color, 10%);
+@background-darker-color: darken(@background-color, 20%);
+@background-darker-secondary-color: darken(@background-color, 15%);
+@background-dark-color: @primary-color;
+@text-color: darken(@primary-color, 20%);
+@text-secondary-color: lighten(@text-color, 10%);
+@text-tertiary-color: lighten(@text-color, 20%);
+@border-light-color: lighten(@text-color, 50%);
+@text-darker-color: darken(@text-color, 20%);
+@white: @background-lighter-color;
+
+@node-opacity-blurred: 0.6;
+@node-highlight-fill-opacity: 0.3;
+@node-highlight-stroke-opacity: 0.5;
+@node-highlight-stroke-width: 3px;
+@node-border-stroke-width: 6px;
+@node-pseudo-border-stroke-width: 2px;
+@node-pseudo-opacity: 1;
+@edge-highlight-opacity: 0.3;
+@edge-opacity-blurred: 0;
+@edge-opacity: 0.5;
+@edge-link-stroke-width: 3px;
+
+@btn-opacity-default: 1;
+@btn-opacity-hover: 1;
+@btn-opacity-selected: 1;
+
+@link-opacity-default: 1;
diff --git a/client/app/styles/main.less b/client/app/styles/main.less
index 80202f17e9..ca1817f588 100644
--- a/client/app/styles/main.less
+++ b/client/app/styles/main.less
@@ -27,7 +27,6 @@
@background-darker-color: darken(@background-color, 8%);
@background-darker-secondary-color: darken(@background-color, 4%);
@background-dark-color: @primary-color;
-@background-medium-color: lighten(@background-dark-color, 55%);
@text-color: lighten(@primary-color, 10%);
@text-secondary-color: lighten(@primary-color, 27%);
@text-tertiary-color: lighten(@primary-color, 50%);
@@ -40,6 +39,24 @@
@terminal-header-height: 34px;
+@node-opacity-blurred: 0.25;
+@node-highlight-fill-opacity: 0.1;
+@node-highlight-stroke-opacity: 0.4;
+@node-highlight-stroke-width: 1px;
+@node-border-stroke-width: 3px;
+@node-pseudo-border-stroke-width: 1px;
+@node-pseudo-opacity: 0.8;
+@edge-highlight-opacity: 0.1;
+@edge-opacity-blurred: 0.2;
+@edge-opacity: 0.5;
+@edge-link-stroke-width: 1px;
+
+@btn-opacity-default: 0.7;
+@btn-opacity-hover: 1;
+@btn-opacity-selected: 0.9;
+
+@link-opacity-default: 0.8;
+
/* add this class to truncate text with ellipsis, container needs width */
.truncate {
white-space: nowrap;
@@ -77,12 +94,12 @@
.btn-opacity {
.palable;
- opacity: 0.8;
+ opacity: @btn-opacity-default;
&-selected {
- opacity: 0.9;
+ opacity: @btn-opacity-selected;
}
&:hover {
- opacity: 1;
+ opacity: @btn-opacity-hover;
}
}
@@ -170,6 +187,23 @@ h2 {
&-label {
text-transform: uppercase;
margin: 0 0.25em;
+
+ &-icon {
+ margin-left: 0.5em;
+ padding: 4px 3px;
+ color: @text-color;
+ position: relative;
+ border: 1px solid transparent;
+ border-radius: 10%;
+ &:hover {
+ border: 1px solid @text-secondary-color;
+ }
+ span {
+ font-size: 150%;
+ position: relative;
+ top: 2px;
+ }
+ }
}
}
@@ -206,10 +240,9 @@ h2 {
.topologies-sub-item {
// border: 1px solid @background-darker-secondary-color;
color: @text-secondary-color;
- .palable;
+ .btn-opacity;
cursor: pointer;
padding: 4px 8px;
- opacity: 0.7;
border-radius: 4px;
opacity: 0.8;
margin-bottom: 3px;
@@ -222,10 +255,6 @@ h2 {
&-active {
opacity: 0.85;
}
-
- &:hover {
- opacity: 1;
- }
}
.topologies-sub-item {
@@ -280,7 +309,7 @@ h2 {
transition: opacity .5s ease-in-out;
&.pseudo {
- opacity: 0.8;
+ opacity: @node-pseudo-opacity;
cursor: default;
.node-label {
@@ -293,12 +322,12 @@ h2 {
.border {
stroke: @text-tertiary-color;
- stroke-width: 1px;
+ stroke-width: @node-pseudo-border-stroke-width;
}
}
&.blurred {
- opacity: 0.25;
+ opacity: @node-opacity-blurred;
}
}
@@ -306,14 +335,14 @@ h2 {
transition: opacity .5s ease-in-out;
&.blurred {
- opacity: 0.2;
+ opacity: @edge-opacity-blurred;
}
.link {
stroke: @text-secondary-color;
- stroke-width: 1px;
+ stroke-width: @edge-link-stroke-width;
fill: none;
- stroke-opacity: 0.5;
+ stroke-opacity: @edge-opacity;
}
.shadow {
stroke: @weave-blue;
@@ -323,14 +352,14 @@ h2 {
}
&.highlighted {
.shadow {
- stroke-opacity: 0.1;
+ stroke-opacity: @edge-highlight-opacity;
}
}
}
circle.border {
- stroke-width: 3px;
+ stroke-width: @node-border-stroke-width;
fill: none;
}
@@ -345,10 +374,10 @@ h2 {
circle.highlighted {
fill: @weave-blue;
- fill-opacity: 0.1;
+ fill-opacity: @node-highlight-fill-opacity;
stroke: @weave-blue;
- stroke-width: 1px;
- stroke-opacity: 0.4;
+ stroke-width: @node-highlight-stroke-width;
+ stroke-opacity: @node-highlight-stroke-opacity;
}
}
@@ -390,18 +419,16 @@ h2 {
right: 8px;
span {
- .palable;
+ .btn-opacity;
padding: 4px 5px;
margin-left: 2px;
font-size: 110%;
color: @white;
cursor: pointer;
- opacity: 0.7;
border: 1px solid rgba(255, 255, 255, 0);
border-radius: 10%;
&:hover {
- opacity: 1;
border-color: rgba(255, 255, 255, 0.6);
}
}
@@ -440,34 +467,25 @@ h2 {
&-link {
.truncate;
- .palable;
+ .btn-opacity;
display: inline-block;
margin-right: 0.5em;
cursor: pointer;
text-decoration: underline;
- opacity: 0.8;
+ opacity: @link-opacity-default;
max-width: 12em;
-
- &:hover {
- opacity: 1;
- }
}
&-more {
- .palable;
+ .btn-opacity;
padding: 0 2px;
text-transform: uppercase;
cursor: pointer;
- opacity: 0.7;
font-size: 60%;
font-weight: bold;
display: inline-block;
position: relative;
top: -5px;
-
- &:hover {
- opacity: 1;
- }
}
}
@@ -480,17 +498,15 @@ h2 {
}
.node-control-button {
- .palable;
+ .btn-opacity;
padding: 6px;
margin-left: 2px;
font-size: 110%;
color: @white;
cursor: pointer;
- opacity: 0.7;
border: 1px solid rgba(255, 255, 255, 0);
border-radius: 10%;
&:hover {
- opacity: 1;
border-color: rgba(255, 255, 255, 0.6);
}
&-pending, &-pending:hover {
@@ -534,8 +550,7 @@ h2 {
margin-top: 48px;
text-align: center;
font-size: 48px;
- color: @background-medium-color;
- opacity: 0.7;
+ color: @text-tertiary-color;
}
&-section {
@@ -557,7 +572,7 @@ h2 {
text-align: center;
&-expand {
- .palable;
+ .btn-opacity;
margin: 4px 16px 0;
border-top: 1px solid @border-light-color;
text-transform: uppercase;
@@ -565,15 +580,11 @@ h2 {
color: @text-secondary-color;
width: 100%;
cursor: pointer;
- opacity: 0.8;
-
- &:hover {
- opacity: 1.0;
- }
+ opacity: @link-opacity-default;
}
&-overflow {
- .palable;
+ .btn-opacity;
display: flex;
flex-direction: row;
flex-wrap: wrap;
@@ -584,10 +595,6 @@ h2 {
position: relative;
padding-bottom: 16px;
- &:hover {
- opacity: 1;
- }
-
&-expand {
text-transform: uppercase;
font-size: 70%;
@@ -698,18 +705,13 @@ h2 {
}
&-more {
- .palable;
+ .btn-opacity;
padding: 2px 0;
text-transform: uppercase;
cursor: pointer;
color: @text-secondary-color;
- opacity: 0.7;
font-size: 80%;
font-weight: bold;
-
- &:hover {
- opacity: 1;
- }
}
&-node {
@@ -721,14 +723,10 @@ h2 {
}
&-link {
- .palable;
+ .btn-opacity;
text-decoration: underline;
cursor: pointer;
- opacity: 0.8;
-
- &:hover {
- opacity: 1;
- }
+ opacity: @link-opacity-default;
}
&-value, &-metric {
@@ -910,6 +908,7 @@ h2 {
cursor: pointer;
font-size: 90%;
margin-left: 0.5em;
+ opacity: @link-opacity-default;
}
}
}
diff --git a/client/build/contrast.html b/client/build/contrast.html
new file mode 100644
index 0000000000..11c0941ef2
--- /dev/null
+++ b/client/build/contrast.html
@@ -0,0 +1,20 @@
+
+
+
+
+ Weave Scope
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/server.js b/client/server.js
index ac8223c105..6efba3ec9d 100644
--- a/client/server.js
+++ b/client/server.js
@@ -19,7 +19,7 @@ var app = express();
// Serve application file depending on environment
-app.get(/(app|terminal-app|vendors).js/, function(req, res) {
+app.get(/(app|contrast-app|terminal-app|vendors).js/, function(req, res) {
var filename = req.originalUrl;
if (process.env.NODE_ENV === 'production') {
res.sendFile(__dirname + '/build' + filename);
diff --git a/client/webpack.local.config.js b/client/webpack.local.config.js
index 19416e1f58..51604fcc39 100644
--- a/client/webpack.local.config.js
+++ b/client/webpack.local.config.js
@@ -28,6 +28,11 @@ module.exports = {
'webpack-dev-server/client?http://localhost:4041',
'webpack/hot/only-dev-server'
],
+ 'contrast-app': [
+ './app/scripts/contrast-main',
+ 'webpack-dev-server/client?http://localhost:4041',
+ 'webpack/hot/only-dev-server'
+ ],
'terminal-app': [
'./app/scripts/terminal-main',
'webpack-dev-server/client?http://localhost:4041',
diff --git a/client/webpack.production.config.js b/client/webpack.production.config.js
index a8a4d65769..1d56404b23 100644
--- a/client/webpack.production.config.js
+++ b/client/webpack.production.config.js
@@ -18,6 +18,7 @@ module.exports = {
entry: {
app: './app/scripts/main',
+ 'contrast-app': './app/scripts/contrast-main',
'terminal-app': './app/scripts/terminal-main',
vendors: ['classnames', 'd3', 'dagre', 'flux', 'immutable',
'lodash', 'page', 'react', 'react-dom', 'react-motion']