Make sorting and resizing the chart in TSVB keyboard accessible, fix #14343 (#15058) (#15075)

* Allow resizing charts via keyboard

* Use React updater function

* Allow sorting of series via keyboard
This commit is contained in:
Tim Roes 2017-11-21 10:06:45 +01:00 committed by GitHub
parent 37682b7074
commit a1313e223a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 20 deletions

View file

@ -0,0 +1,13 @@
import { keyCodes } from 'ui_framework/services';
export function createUpDownHandler(callback) {
return (ev) => {
if (ev.keyCode === keyCodes.UP) {
ev.preventDefault();
callback('up');
} else if (ev.keyCode === keyCodes.DOWN) {
ev.preventDefault();
callback('down');
}
};
}

View file

@ -65,6 +65,7 @@ class Series extends Component {
onDelete: this.props.onDelete,
onMouseDown: this.props.onMouseDown,
onTouchStart: this.props.onTouchStart,
onShouldSortItem: this.props.onShouldSortItem,
onSortableItemMount: this.props.onSortableItemMount,
onSortableItemReadyToMove: this.props.onSortableItemReadyToMove,
model: this.props.model,
@ -98,6 +99,7 @@ Series.propTypes = {
onClone: PropTypes.func,
onDelete: PropTypes.func,
onMouseDown: PropTypes.func,
onShouldSortItem: PropTypes.func.isRequired,
onSortableItemMount: PropTypes.func,
onSortableItemReadyToMove: PropTypes.func,
onTouchStart: PropTypes.func,

View file

@ -15,6 +15,7 @@ class SeriesEditor extends Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.sortSeries = this.sortSeries.bind(this);
}
handleClone(series) {
@ -22,7 +23,21 @@ class SeriesEditor extends Component {
handleAdd.call(null, this.props, () => newSeries);
}
renderRow(row) {
sortSeries(index, direction, allSeries) {
const newIndex = index + (direction === 'up' ? -1 : 1);
if (newIndex < 0 || newIndex >= allSeries.length) {
// Don't do anything when series is already at the edge
return;
}
const newSeries = allSeries.slice(0);
const changeWithElement = allSeries[newIndex];
newSeries[newIndex] = allSeries[index];
newSeries[index] = changeWithElement;
this.props.onChange({ series: newSeries });
}
renderRow(row, index, allSeries) {
const { props } = this;
const { fields, model, name, limit, colorPicker } = props;
return (
@ -36,6 +51,7 @@ class SeriesEditor extends Component {
onChange={handleChange.bind(null, props)}
onClone={() => this.handleClone(row)}
onDelete={handleDelete.bind(null, props, row)}
onShouldSortItem={(direction) => this.sortSeries(index, direction, allSeries)}
model={row}
panel={model}
sortData={row.id}

View file

@ -1,20 +1,24 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { keyCodes } from 'ui_framework/services';
import Visualization from './visualization';
import Toggle from 'react-toggle';
import 'react-toggle/style.css';
const MIN_CHART_HEIGHT = 250;
class VisEditorVisualization extends Component {
constructor(props) {
super(props);
this.state = {
height: 250,
height: MIN_CHART_HEIGHT,
dragging: false
};
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.onSizeHandleKeyDown = this.onSizeHandleKeyDown.bind(this);
}
handleMouseDown() {
@ -28,10 +32,9 @@ class VisEditorVisualization extends Component {
componentWillMount() {
this.handleMouseMove = (event) => {
if (this.state.dragging) {
const height = this.state.height + event.movementY;
if (height > 250) {
this.setState({ height });
}
this.setState((prevState) => ({
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY)
}));
}
};
window.addEventListener('mousemove', this.handleMouseMove);
@ -48,6 +51,25 @@ class VisEditorVisualization extends Component {
el.setAttribute('render-counter', 'disabled');
}
/**
* Resize the chart height when pressing up/down while the drag handle
* for resizing has the focus.
* We use 15px steps to do the scaling and make sure the chart has at least its
* defined minimum width (MIN_CHART_HEIGHT).
*/
onSizeHandleKeyDown(ev) {
const { keyCode } = ev;
if (keyCode === keyCodes.UP || keyCode === keyCodes.DOWN) {
ev.preventDefault();
this.setState((prevState) => {
const newHeight = prevState.height + (keyCode === keyCodes.UP ? -15 : 15);
return {
height: Math.max(MIN_CHART_HEIGHT, newHeight)
};
});
}
}
render() {
const { dirty, autoApply } = this.props;
const style = { height: this.state.height };
@ -117,14 +139,15 @@ class VisEditorVisualization extends Component {
</div>
<div className="vis-editor-hide-for-reporting">
{applyButton}
<div
aria-hidden="true"
<button
className="vis_editor__visualization-draghandle"
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
onKeyDown={this.onSizeHandleKeyDown}
aria-label="Press up/down to adjust the chart size"
>
<i className="fa fa-ellipsis-h" />
</div>
</button>
</div>
</div>
);

View file

@ -8,6 +8,7 @@ import Split from '../../split';
import Tooltip from '../../tooltip';
import createAggRowRender from '../../lib/create_agg_row_render';
import createTextHandler from '../../lib/create_text_handler';
import { createUpDownHandler } from '../../lib/sort_keyhandler';
function GaugeSeries(props) {
const {
@ -111,9 +112,13 @@ function GaugeSeries(props) {
if (!props.disableDelete) {
dragHandle = (
<Tooltip text="Sort">
<div className="vis_editor__sort thor__button-outlined-default sm">
<button
className="vis_editor__sort thor__button-outlined-default sm"
aria-label="Sort series by pressing up/down"
onKeyDown={createUpDownHandler(props.onShouldSortItem)}
>
<i className="fa fa-sort" />
</div>
</button>
</Tooltip>
);
}

View file

@ -8,6 +8,7 @@ import Split from '../../split';
import Tooltip from '../../tooltip';
import createAggRowRender from '../../lib/create_agg_row_render';
import createTextHandler from '../../lib/create_text_handler';
import { createUpDownHandler } from '../../lib/sort_keyhandler';
function MetricSeries(props) {
const {
@ -114,9 +115,13 @@ function MetricSeries(props) {
if (!props.disableDelete) {
dragHandle = (
<Tooltip text="Sort">
<div className="vis_editor__sort thor__button-outlined-default sm">
<button
className="vis_editor__sort thor__button-outlined-default sm"
aria-label="Sort series by pressing up/down"
onKeyDown={createUpDownHandler(props.onShouldSortItem)}
>
<i className="fa fa-sort" />
</div>
</button>
</Tooltip>
);
}

View file

@ -5,6 +5,7 @@ import Sortable from 'react-anything-sortable';
import Tooltip from '../../tooltip';
import createTextHandler from '../../lib/create_text_handler';
import createAggRowRender from '../../lib/create_agg_row_render';
import { createUpDownHandler } from '../../lib/sort_keyhandler';
function TopNSeries(props) {
const {
@ -84,9 +85,13 @@ function TopNSeries(props) {
if (!props.disableDelete) {
dragHandle = (
<Tooltip text="Sort">
<div className="vis_editor__sort thor__button-outlined-default sm">
<button
className="vis_editor__sort thor__button-outlined-default sm"
aria-label="Sort series by pressing up/down"
onKeyDown={createUpDownHandler(props.onShouldSortItem)}
>
<i className="fa fa-sort" />
</div>
</button>
</Tooltip>
);
}
@ -155,4 +160,3 @@ TopNSeries.propTypes = {
};
export default TopNSeries;

View file

@ -8,6 +8,7 @@ import Tooltip from '../../tooltip';
import Split from '../../split';
import createAggRowRender from '../../lib/create_agg_row_render';
import createTextHandler from '../../lib/create_text_handler';
import { createUpDownHandler } from '../../lib/sort_keyhandler';
function TimeseriesSeries(props) {
const {
@ -111,9 +112,13 @@ function TimeseriesSeries(props) {
if (!props.disableDelete) {
dragHandle = (
<Tooltip text="Sort">
<div className="vis_editor__sort thor__button-outlined-default sm">
<button
className="vis_editor__sort thor__button-outlined-default sm"
aria-label="Sort series by pressing up/down"
onKeyDown={createUpDownHandler(props.onShouldSortItem)}
>
<i className="fa fa-sort" />
</div>
</button>
</Tooltip>
);
}
@ -175,6 +180,7 @@ TimeseriesSeries.propTypes = {
onClone: PropTypes.func,
onDelete: PropTypes.func,
onMouseDown: PropTypes.func,
onShouldSortItem: PropTypes.func.isRequired,
onSortableItemMount: PropTypes.func,
onSortableItemReadyToMove: PropTypes.func,
onTouchStart: PropTypes.func,

View file

@ -8,6 +8,7 @@ import Split from '../../split';
import Tooltip from '../../tooltip';
import createTextHandler from '../../lib/create_text_handler';
import createAggRowRender from '../../lib/create_agg_row_render';
import { createUpDownHandler } from '../../lib/sort_keyhandler';
function TopNSeries(props) {
const {
@ -109,9 +110,13 @@ function TopNSeries(props) {
if (!props.disableDelete) {
dragHandle = (
<Tooltip text="Sort">
<div className="vis_editor__sort thor__button-outlined-default sm">
<button
className="vis_editor__sort thor__button-outlined-default sm"
aria-label="Sort series by pressing up/down"
onKeyDown={createUpDownHandler(props.onShouldSortItem)}
>
<i className="fa fa-sort" />
</div>
</button>
</Tooltip>
);
}

View file

@ -298,9 +298,18 @@
text-align: center;
color: @grayLight;
cursor: row-resize;
width: 100%;
display: block;
appearance: none;
background: none;
border: none;
&:hover {
color: @gray;
}
&:focus {
color: @esBlue;
box-shadow: none;
}
}
.vis_editor__visualization-title {