From 0a9f26bb60e7a505bee1ccfe6a9fc7f293829c2c Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 7 Apr 2016 18:33:34 +0200 Subject: [PATCH] Adds a shortcut panel! --- client/app/scripts/actions/app-actions.js | 20 ++++++- client/app/scripts/components/app.js | 9 ++- client/app/scripts/components/help-panel.js | 43 +++++++++++++++ client/app/scripts/constants/action-types.js | 4 +- client/app/scripts/stores/app-store.js | 15 +++++ client/app/styles/main.less | 58 ++++++++++++++++++++ 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 client/app/scripts/components/help-panel.js diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 36274ce29..61327d4bb 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -13,6 +13,22 @@ import AppStore from '../stores/app-store'; const log = debug('scope:app-actions'); +export function showHelp() { + AppDispatcher.dispatch({type: ActionTypes.SHOW_HELP}); +} + +export function hideHelp() { + AppDispatcher.dispatch({type: ActionTypes.HIDE_HELP}); +} + +export function toggleHelp() { + if (AppStore.getShowingHelp()) { + hideHelp(); + } else { + showHelp(); + } +} + export function selectMetric(metricId) { AppDispatcher.dispatch({ type: ActionTypes.SELECT_METRIC, @@ -219,7 +235,9 @@ export function enterNode(nodeId) { export function hitEsc() { const controlPipe = AppStore.getControlPipe(); - if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') { + if (AppStore.getShowingHelp()) { + hideHelp(); + } else if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') { AppDispatcher.dispatch({ type: ActionTypes.CLICK_CLOSE_TERMINAL, pipeId: controlPipe.get('id') diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 1b00cc961..ab9babe27 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -7,12 +7,13 @@ import Logo from './logo'; import AppStore from '../stores/app-store'; import Footer from './footer.js'; import Sidebar from './sidebar.js'; +import HelpPanel from './help-panel'; import Status from './status.js'; import Topologies from './topologies.js'; import TopologyOptions from './topology-options.js'; import { getApiDetails, getTopologies } from '../utils/web-api-utils'; import { pinNextMetric, hitEsc, unpinMetric, - selectMetric } from '../actions/app-actions'; + selectMetric, toggleHelp } from '../actions/app-actions'; import Details from './details'; import Nodes from './nodes'; import MetricSelector from './metric-selector'; @@ -43,6 +44,7 @@ function getStateFromStores() { availableCanvasMetrics: AppStore.getAvailableCanvasMetrics(), nodeDetails: AppStore.getNodeDetails(), nodes: AppStore.getNodes(), + showingHelp: AppStore.getShowingHelp(), selectedNodeId: AppStore.getSelectedNodeId(), selectedMetric: AppStore.getSelectedMetric(), topologies: AppStore.getTopologies(), @@ -113,6 +115,8 @@ export default class App extends React.Component { } else if (char === 'd') { toggleDebugToolbar(); this.forceUpdate(); + } else if (char === '?') { + toggleHelp(); } } @@ -127,6 +131,9 @@ export default class App extends React.Component { return (
{showingDebugToolbar() && } + + {this.state.showingHelp && } + {showingDetails &&
} diff --git a/client/app/scripts/components/help-panel.js b/client/app/scripts/components/help-panel.js new file mode 100644 index 000000000..3c28914e1 --- /dev/null +++ b/client/app/scripts/components/help-panel.js @@ -0,0 +1,43 @@ +import React from 'react'; + +const GENERAL_SHORTCUTS = [ + {key: 'esc', label: 'Close panel'}, +]; + +const CANVAS_METRIC_SHORTCUTS = [ + {key: '<', label: 'Pin previous metric'}, + {key: '>', label: 'Pin next metric'}, + {key: 'q', label: 'Unpin current metric'}, +]; + +function renderShortcuts(cuts) { + return ( +
+ {cuts.map(({key, label}) => ( +
+
{key}
+
{label}
+
+ ))} +
+ ); +} + +export default class HelpPanel extends React.Component { + render() { + return ( +
+
+

Keyboard Shortcuts

+
+
+

General

+ {renderShortcuts(GENERAL_SHORTCUTS)} +

Canvas Metrics

+ {renderShortcuts(CANVAS_METRIC_SHORTCUTS)} +
+
+ ); + } +} + diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index a6b3f5912..b6290f2fb 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -21,6 +21,7 @@ const ACTION_TYPES = [ 'DO_CONTROL_SUCCESS', 'ENTER_EDGE', 'ENTER_NODE', + 'HIDE_HELP', 'LEAVE_EDGE', 'LEAVE_NODE', 'PIN_METRIC', @@ -36,7 +37,8 @@ const ACTION_TYPES = [ 'RECEIVE_API_DETAILS', 'RECEIVE_ERROR', 'ROUTE_TOPOLOGY', - 'SELECT_METRIC' + 'SELECT_METRIC', + 'SHOW_HELP' ]; export default _.zipObject(ACTION_TYPES, ACTION_TYPES); diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index 9f11ac536..646e11f10 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -58,6 +58,7 @@ let routeSet = false; let controlPipes = makeOrderedMap(); // pipeId -> controlPipe let updatePausedAt = null; // Date let websocketClosed = true; +let showingHelp = false; let selectedMetric = null; let pinnedMetric = selectedMetric; @@ -149,6 +150,10 @@ export class AppStore extends Store { }; } + getShowingHelp() { + return showingHelp; + } + getActiveTopologyOptions() { // options for current topology, sub-topologies share options with parent if (currentTopology && currentTopology.get('parentId')) { @@ -449,6 +454,16 @@ export class AppStore extends Store { this.__emitChange(); break; } + case ActionTypes.SHOW_HELP: { + showingHelp = true; + this.__emitChange(); + break; + } + case ActionTypes.HIDE_HELP: { + showingHelp = false; + this.__emitChange(); + break; + } case ActionTypes.DESELECT_NODE: { closeNodeDetails(); this.__emitChange(); diff --git a/client/app/styles/main.less b/client/app/styles/main.less index fe9d46fe1..d3f815c07 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -1080,6 +1080,64 @@ h2 { } } +// +// Help panel! +// + +.help-panel { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2048; + .shadow-2; + + &-header { + background-color: @weave-blue; + padding: 36px; + color: white; + + h2 { + margin: 0; + } + } + + &-main { + padding: 12px 36px 36px; + background-color: white; + } + + h3 { + text-transform: uppercase; + font-size: 90%; + color: #8383ac; + padding: 4px 0; + } + + &-shortcut { + kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #555; + vertical-align: middle; + background-color: #fcfcfc; + border: solid 1px #ccc; + border-bottom-color: #bbb; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #bbb; + } + div.key { + width: 100px; + display: inline-block; + } + div.label { + display: inline-block; + } + } +} + // // Debug panel! //