Skip to content

Commit

Permalink
Change scrolling behaviour to lock headers in place
Browse files Browse the repository at this point in the history
  • Loading branch information
foot committed Jul 14, 2016
1 parent 0e2162b commit fe049c8
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 53 deletions.
8 changes: 7 additions & 1 deletion client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export function toggleHelp() {


export function toggleGridMode(enabled) {
return {type: ActionTypes.SET_GRID_MODE, enabled};
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SET_GRID_MODE,
enabled
});
updateRoute(getState);
};
}


Expand Down
12 changes: 10 additions & 2 deletions client/app/scripts/charts/nodes-grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ export default class NodesGrid extends React.Component {
const { margins, nodes, height } = this.props;
const cmpStyle = {
height,
paddingTop: margins.top,
paddingBottom: margins.bottom,
marginTop: margins.top,
paddingLeft: margins.left,
paddingRight: margins.right,
};
const tbodyHeight = height - 24 - 18;
const className = 'scroll-body';
const tbodyStyle = {
height: `${tbodyHeight}px`,
paddingBottom: 160
};

const detailsData = {
label: 'procs',
Expand All @@ -52,6 +57,9 @@ export default class NodesGrid extends React.Component {
<div className="nodes-grid">
<NodeDetailsTable
style={cmpStyle}
className={className}
tbodyStyle={tbodyStyle}
topologyId={this.props.topologyId}
onMouseOut={this.onMouseOut}
onMouseOverRow={this.onMouseOverRow}
{...detailsData}
Expand Down
17 changes: 11 additions & 6 deletions client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Topologies from './topologies.js';
import TopologyOptions from './topology-options.js';
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric,
selectMetric, toggleHelp } from '../actions/app-actions';
selectMetric, toggleHelp, toggleGridMode } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
import GridModeSelector from './grid-mode-selector';
Expand Down Expand Up @@ -86,6 +86,10 @@ class App extends React.Component {
dispatch(pinNextMetric(-1));
} else if (char === '>') {
dispatch(pinNextMetric(1));
} else if (char === 't') {
dispatch(toggleGridMode(false));
} else if (char === 'g') {
dispatch(toggleGridMode(true));
} else if (char === 'q') {
dispatch(unpinMetric());
dispatch(selectMetric(null));
Expand All @@ -99,8 +103,8 @@ class App extends React.Component {
}

render() {
const { showingDetails, showingHelp, showingMetricsSelector, showingNetworkSelector,
showingTerminal } = this.props;
const { gridMode, showingDetails, showingHelp, showingMetricsSelector,
showingNetworkSelector, showingTerminal } = this.props;

return (
<div className="app">
Expand All @@ -124,11 +128,11 @@ class App extends React.Component {

<Nodes />

<Sidebar>
<Sidebar classNames={gridMode ? 'sidebar-gridmode' : ''}>
{showingMetricsSelector && !gridMode && <MetricSelector />}
{showingNetworkSelector && !gridMode && <NetworkSelector />}
<Status />
<GridModeSelector />
{showingMetricsSelector && <MetricSelector />}
{showingNetworkSelector && <NetworkSelector />}
<TopologyOptions />
</Sidebar>

Expand All @@ -141,6 +145,7 @@ class App extends React.Component {
function mapStateToProps(state) {
return {
activeTopologyOptions: getActiveTopologyOptions(state),
gridMode: state.get('gridMode'),
routeSet: state.get('routeSet'),
searchFocused: state.get('searchFocused'),
searchQuery: state.get('searchQuery'),
Expand Down
60 changes: 60 additions & 0 deletions client/app/scripts/components/grid-mode-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';

import { toggleGridMode } from '../actions/app-actions';

class GridModeSelector extends React.Component {

constructor(props, context) {
super(props, context);

this.enableGridMode = this.enableGridMode.bind(this);
this.disableGridMode = this.disableGridMode.bind(this);
}

enableGridMode() {
return this.props.toggleGridMode(true);
}

disableGridMode() {
return this.props.toggleGridMode(false);
}

renderItem(icons, isSelected, onClick) {
const className = classNames('network-selector-action', {
'network-selector-action-selected': isSelected
});
return (
<div
className={className}
onClick={onClick} >
<span className={icons} style={{fontSize: 16}} />
</div>
);
}

render() {
const { gridMode } = this.props;

return (
<div className="network-selector">
<div className="network-selector-wrapper">
{this.renderItem('fa fa-th', !gridMode, this.disableGridMode)}
{this.renderItem('fa fa-bars', gridMode, this.enableGridMode)}
</div>
</div>
);
}
}

function mapStateToProps(state) {
return {
gridMode: state.get('gridMode'),
};
}

export default connect(
mapStateToProps,
{ toggleGridMode }
)(GridModeSelector);
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { formatMetric } from '../../utils/string-utils';

function NodeDetailsTableNodeMetric(props) {
return (
<td className="node-details-table-node-metric">
<td className="node-details-table-node-metric" style={props.style}>
{formatMetric(props.value, props)}
</td>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ function getValuesForNode(node) {
}


function renderValues(node, columns = []) {
function renderValues(node, columns = [], columnWidths = []) {
const fields = getValuesForNode(node);
return columns.map(({id}) => {
return columns.map(({id}, i) => {
const field = fields[id];
const style = { width: columnWidths[i] };
if (field) {
if (field.valueType === 'metadata') {
return (
<td className="node-details-table-node-value truncate" title={field.value}
style={style}
key={field.id}>
{field.value}
</td>
);
}
return <NodeDetailsTableNodeMetric key={field.id} {...field} />;
return <NodeDetailsTableNodeMetric style={style} key={field.id} {...field} />;
}
// empty cell to complete the row for proper hover
return <td className="node-details-table-node-value" key={id} />;
return <td className="node-details-table-node-value" style={style} key={id} />;
});
}

Expand All @@ -53,13 +55,15 @@ export default class NodeDetailsTableRow extends React.Component {
}

render() {
const { node, nodeIdKey, topologyId, columns, onMouseOverRow, selected } = this.props;
const values = renderValues(node, columns);
const { node, nodeIdKey, topologyId, columns, onMouseOverRow, selected, widths } = this.props;
const [firstColumnWidth, ...columnWidths] = widths;
console.log(widths);
const values = renderValues(node, columns, columnWidths);
const nodeId = node[nodeIdKey];
const className = classNames('node-details-table-node', { selected });
return (
<tr onMouseOver={onMouseOverRow && this.onMouseOver} className={className}>
<td className="node-details-table-node-label truncate">
<td className="node-details-table-node-label truncate" style={{ width: firstColumnWidth }}>
<NodeDetailsTableNodeLink
topologyId={topologyId}
nodeId={nodeId}
Expand Down
75 changes: 49 additions & 26 deletions client/app/scripts/components/node-details/node-details-table.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash';
import React from 'react';
import { Map as makeMap } from 'immutable';
import classNames from 'classnames';

import ShowMore from '../show-more';
import NodeDetailsTableRow from './node-details-table-row';
Expand All @@ -13,9 +14,16 @@ function isNumberField(field) {

const COLUMN_WIDTHS = {
port: '44px',
count: '70px'
count: '70px',
process_cpu_usage_percent: '80px',
threads: '80px',
process_memory_usage_bytes: '80px',
open_files_count: '80px',
ppid: '80px',
pid: '80px',
};


function getDefaultSortBy(columns, nodes) {
// default sorter specified by columns
const defaultSortColumn = _.find(columns, {defaultSort: true});
Expand Down Expand Up @@ -75,6 +83,31 @@ function getSortedNodes(nodes, columns, sortBy, sortedDesc) {
}


function getColumnsWidths(headers) {
return headers.map((h, i) => {
//
// Beauty hack: adjust first column width if there are only few columns;
// this assumes the other columns are narrow metric columns of 20% table width
//
if (i === 0) {
if (headers.length === 2) {
return '66%';
} else if (headers.length === 3) {
return '50%';
} else if (headers.length >= 3 && headers.length < 5) {
return '33%';
}
}

//
// More beauty hacking, ports and counts can only get so big, free up WS for other longer
// fields like IPs!
//
return COLUMN_WIDTHS[h.id];
});
}


export default class NodeDetailsTable extends React.Component {

constructor(props, context) {
Expand All @@ -101,33 +134,20 @@ export default class NodeDetailsTable extends React.Component {
this.setState({limit});
}

getColumnHeaders() {
const columns = this.props.columns || [];
return [{id: 'label', label: this.props.label}].concat(columns);
}

renderHeaders() {
if (this.props.nodes && this.props.nodes.length > 0) {
const columns = this.props.columns || [];
const headers = [{id: 'label', label: this.props.label}].concat(columns);
const headers = this.getColumnHeaders();
const widths = getColumnsWidths(headers);
const defaultSortBy = getDefaultSortBy(this.props);

// Beauty hack: adjust first column width if there are only few columns;
// this assumes the other columns are narrow metric columns of 20% table width
if (headers.length === 2) {
headers[0].width = '66%';
} else if (headers.length === 3) {
headers[0].width = '50%';
} else if (headers.length >= 3 && headers.length < 5) {
headers[0].width = '33%';
}

//
// More beauty hacking, ports and counts can only get so big, free up WS for other longer
// fields like IPs!
//
headers.forEach(h => {
h.width = COLUMN_WIDTHS[h.id];
});

return (
<tr>
{headers.map(header => {
{headers.map((header, i) => {
const headerClasses = ['node-details-table-header', 'truncate'];
const onHeaderClick = ev => {
this.handleHeaderClick(ev, header.id);
Expand All @@ -144,8 +164,8 @@ export default class NodeDetailsTable extends React.Component {

// set header width in percent
const style = {};
if (header.width) {
style.width = header.width;
if (widths[i]) {
style.width = widths[i];
}

return (
Expand Down Expand Up @@ -183,21 +203,24 @@ export default class NodeDetailsTable extends React.Component {
React.cloneElement(child, { nodeOrder })
));

const className = classNames('node-details-table-wrapper-wrapper', this.props.className);

return (
<div className="node-details-table-wrapper-wrapper" style={this.props.style}>
<div className={className} style={this.props.style}>
<div className="node-details-table-wrapper" onMouseOut={this.props.onMouseOut}>
<table className="node-details-table">
<thead>
{headers}
</thead>
<tbody>
<tbody style={this.props.tbodyStyle}>
{nodes && nodes.map(node => (
<NodeDetailsTableRow
key={node.id}
selected={this.props.highlightedNodeIds &&
this.props.highlightedNodeIds.has(node.id)}
node={node}
nodeIdKey={nodeIdKey}
widths={getColumnsWidths(this.getColumnHeaders())}
columns={columns}
onMouseOverRow={onMouseOverRow}
topologyId={topologyId} />
Expand Down
4 changes: 3 additions & 1 deletion client/app/scripts/components/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NodesError from '../charts/nodes-error';
import { isTopologyEmpty } from '../utils/topology-utils';
import { CANVAS_MARGINS } from '../constants/styles';

const navbarHeight = 160;
const navbarHeight = 168;
const marginTop = 0;

/**
Expand Down Expand Up @@ -75,6 +75,7 @@ class Nodes extends React.Component {
<NodesGrid {...this.state}
nodeSize="24"
nodes={nodes}
topologyId={this.props.currentTopologyId}
margins={CANVAS_MARGINS}
layoutPrecision={layoutPrecision}
highlightedNodeIds={highlightedNodeIds}
Expand Down Expand Up @@ -103,6 +104,7 @@ function mapStateToProps(state) {
return {
gridMode: state.get('gridMode'),
nodes: state.get('nodes'),
currentTopologyId: state.get('currentTopologyId'),
topologyEmpty: isTopologyEmpty(state),
highlightedNodeIds: state.get('highlightedNodeIds')
};
Expand Down
5 changes: 3 additions & 2 deletions client/app/scripts/components/sidebar.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';

export default function Sidebar({children}) {
export default function Sidebar({children, classNames}) {
const className = `sidebar ${classNames}`;
return (
<div className="sidebar">
<div className={className}>
{children}
</div>
);
Expand Down
Loading

0 comments on commit fe049c8

Please sign in to comment.