mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'develop' of github.com:grafana/grafana into develop
This commit is contained in:
commit
b0c014bf24
@ -1,124 +1,117 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import withKeyboardNavigation from './withKeyboardNavigation';
|
import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation';
|
||||||
import { DataSourceSelectItem } from 'app/types';
|
import { DataSourceSelectItem } from 'app/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
onChangeDataSource: (ds: any) => void;
|
onChangeDataSource: (ds: DataSourceSelectItem) => void;
|
||||||
datasources: DataSourceSelectItem[];
|
datasources: DataSourceSelectItem[];
|
||||||
selected?: number;
|
|
||||||
onKeyDown?: (evt: any, maxSelectedIndex: number, onEnterAction: () => void) => void;
|
|
||||||
onMouseEnter?: (select: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataSourcePicker = withKeyboardNavigation(
|
export class DataSourcePicker extends PureComponent<Props, State> {
|
||||||
class DataSourcePicker extends PureComponent<Props, State> {
|
searchInput: HTMLElement;
|
||||||
searchInput: HTMLElement;
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getDataSources() {
|
|
||||||
const { searchQuery } = this.state;
|
|
||||||
const regex = new RegExp(searchQuery, 'i');
|
|
||||||
const { datasources } = this.props;
|
|
||||||
|
|
||||||
const filtered = datasources.filter(item => {
|
|
||||||
return regex.test(item.name) || regex.test(item.meta.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maxSelectedIndex() {
|
|
||||||
const filtered = this.getDataSources();
|
|
||||||
return filtered.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDataSource = (ds: DataSourceSelectItem, index: number) => {
|
|
||||||
const { onChangeDataSource, selected, onMouseEnter } = this.props;
|
|
||||||
const onClick = () => onChangeDataSource(ds);
|
|
||||||
const isSelected = selected === index;
|
|
||||||
const cssClass = classNames({
|
|
||||||
'ds-picker-list__item': true,
|
|
||||||
'ds-picker-list__item--selected': isSelected,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={cssClass}
|
|
||||||
title={ds.name}
|
|
||||||
onClick={onClick}
|
|
||||||
onMouseEnter={() => onMouseEnter(index)}
|
|
||||||
>
|
|
||||||
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
|
|
||||||
<div className="ds-picker-list__name">{ds.name}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.searchInput.focus();
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearchQueryChange = evt => {
|
|
||||||
const value = evt.target.value;
|
|
||||||
this.setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
searchQuery: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
renderFilters() {
|
|
||||||
const { searchQuery } = this.state;
|
|
||||||
const { onKeyDown } = this.props;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<label className="gf-form--has-input-icon">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="gf-form-input width-13"
|
|
||||||
placeholder=""
|
|
||||||
ref={elem => (this.searchInput = elem)}
|
|
||||||
onChange={this.onSearchQueryChange}
|
|
||||||
value={searchQuery}
|
|
||||||
onKeyDown={evt => {
|
|
||||||
onKeyDown(evt, this.maxSelectedIndex, () => {
|
|
||||||
const { onChangeDataSource, selected } = this.props;
|
|
||||||
const ds = this.getDataSources()[selected];
|
|
||||||
onChangeDataSource(ds);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<i className="gf-form-input-icon fa fa-search" />
|
|
||||||
</label>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="cta-form__bar">
|
|
||||||
{this.renderFilters()}
|
|
||||||
<div className="gf-form--grow" />
|
|
||||||
</div>
|
|
||||||
<div className="ds-picker-list">{this.getDataSources().map(this.renderDataSource)}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
getDataSources() {
|
||||||
|
const { searchQuery } = this.state;
|
||||||
|
const regex = new RegExp(searchQuery, 'i');
|
||||||
|
const { datasources } = this.props;
|
||||||
|
|
||||||
|
const filtered = datasources.filter(item => {
|
||||||
|
return regex.test(item.name) || regex.test(item.meta.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxSelectedIndex() {
|
||||||
|
const filtered = this.getDataSources();
|
||||||
|
return filtered.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDataSource = (ds: DataSourceSelectItem, index: number, keyNavProps: KeyboardNavigationProps) => {
|
||||||
|
const { onChangeDataSource } = this.props;
|
||||||
|
const { selected, onMouseEnter } = keyNavProps;
|
||||||
|
const onClick = () => onChangeDataSource(ds);
|
||||||
|
const isSelected = selected === index;
|
||||||
|
const cssClass = classNames({
|
||||||
|
'ds-picker-list__item': true,
|
||||||
|
'ds-picker-list__item--selected': isSelected,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div key={index} className={cssClass} title={ds.name} onClick={onClick} onMouseEnter={() => onMouseEnter(index)}>
|
||||||
|
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
|
||||||
|
<div className="ds-picker-list__name">{ds.name}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.searchInput.focus();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchQueryChange = evt => {
|
||||||
|
const value = evt.target.value;
|
||||||
|
this.setState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
searchQuery: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
renderFilters({ onKeyDown, selected }: KeyboardNavigationProps) {
|
||||||
|
const { searchQuery } = this.state;
|
||||||
|
return (
|
||||||
|
<label className="gf-form--has-input-icon">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="gf-form-input width-13"
|
||||||
|
placeholder=""
|
||||||
|
ref={elem => (this.searchInput = elem)}
|
||||||
|
onChange={this.onSearchQueryChange}
|
||||||
|
value={searchQuery}
|
||||||
|
onKeyDown={evt => {
|
||||||
|
onKeyDown(evt, this.maxSelectedIndex, () => {
|
||||||
|
const { onChangeDataSource } = this.props;
|
||||||
|
const ds = this.getDataSources()[selected];
|
||||||
|
onChangeDataSource(ds);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<i className="gf-form-input-icon fa fa-search" />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<KeyboardNavigation
|
||||||
|
render={(keyNavProps: KeyboardNavigationProps) => (
|
||||||
|
<>
|
||||||
|
<div className="cta-form__bar">
|
||||||
|
{this.renderFilters(keyNavProps)}
|
||||||
|
<div className="gf-form--grow" />
|
||||||
|
</div>
|
||||||
|
<div className="ds-picker-list">
|
||||||
|
{this.getDataSources().map((ds, index) => this.renderDataSource(ds, index, keyNavProps))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default DataSourcePicker;
|
export default DataSourcePicker;
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import React, { KeyboardEvent, Component } from 'react';
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
selected: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeyboardNavigationProps {
|
||||||
|
onKeyDown: (evt: KeyboardEvent<EventTarget>, maxSelectedIndex: number, onEnterAction: () => void) => void;
|
||||||
|
onMouseEnter: (select: number) => void;
|
||||||
|
selected: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
render: (injectProps: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyboardNavigation extends Component<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selected: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
goToNext = (maxSelectedIndex: number) => {
|
||||||
|
const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1;
|
||||||
|
this.setState({
|
||||||
|
selected: nextIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
goToPrev = (maxSelectedIndex: number) => {
|
||||||
|
const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1;
|
||||||
|
this.setState({
|
||||||
|
selected: nextIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => {
|
||||||
|
if (evt.key === 'ArrowDown') {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.goToNext(maxSelectedIndex);
|
||||||
|
}
|
||||||
|
if (evt.key === 'ArrowUp') {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.goToPrev(maxSelectedIndex);
|
||||||
|
}
|
||||||
|
if (evt.key === 'Enter' && onEnterAction) {
|
||||||
|
onEnterAction();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseEnter = (mouseEnterIndex: number) => {
|
||||||
|
this.setState({
|
||||||
|
selected: mouseEnterIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const injectProps = {
|
||||||
|
onKeyDown: this.onKeyDown,
|
||||||
|
onMouseEnter: this.onMouseEnter,
|
||||||
|
selected: this.state.selected,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{this.props.render({ ...injectProps })}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeyboardNavigation;
|
@ -4,15 +4,15 @@ import _ from 'lodash';
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { PanelPlugin } from 'app/types/plugins';
|
import { PanelPlugin } from 'app/types/plugins';
|
||||||
import VizTypePickerPlugin from './VizTypePickerPlugin';
|
import VizTypePickerPlugin from './VizTypePickerPlugin';
|
||||||
|
import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation';
|
||||||
|
|
||||||
interface Props {
|
export interface Props {
|
||||||
current: PanelPlugin;
|
current: PanelPlugin;
|
||||||
onTypeChanged: (newType: PanelPlugin) => void;
|
onTypeChanged: (newType: PanelPlugin) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
selected: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VizTypePicker extends PureComponent<Props, State> {
|
export class VizTypePicker extends PureComponent<Props, State> {
|
||||||
@ -24,7 +24,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
selected: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,35 +32,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
return filteredPluginList.length - 1;
|
return filteredPluginList.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
goRight = () => {
|
|
||||||
const nextIndex = this.state.selected >= this.maxSelectedIndex ? 0 : this.state.selected + 1;
|
|
||||||
this.setState({
|
|
||||||
selected: nextIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
goLeft = () => {
|
|
||||||
const nextIndex = this.state.selected <= 0 ? this.maxSelectedIndex : this.state.selected - 1;
|
|
||||||
this.setState({
|
|
||||||
selected: nextIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onKeyDown = evt => {
|
|
||||||
if (evt.key === 'ArrowDown') {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.goRight();
|
|
||||||
}
|
|
||||||
if (evt.key === 'ArrowUp') {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.goLeft();
|
|
||||||
}
|
|
||||||
if (evt.key === 'Enter') {
|
|
||||||
const filteredPluginList = this.getFilteredPluginList();
|
|
||||||
this.props.onTypeChanged(filteredPluginList[this.state.selected]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.searchInput.focus();
|
this.searchInput.focus();
|
||||||
@ -78,14 +48,10 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
return _.sortBy(panels, 'sort');
|
return _.sortBy(panels, 'sort');
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseEnter = (mouseEnterIndex: number) => {
|
renderVizPlugin = (plugin: PanelPlugin, index: number, keyNavProps: KeyboardNavigationProps) => {
|
||||||
this.setState({
|
const { onTypeChanged } = this.props;
|
||||||
selected: mouseEnterIndex,
|
const { selected, onMouseEnter } = keyNavProps;
|
||||||
});
|
const isSelected = selected === index;
|
||||||
};
|
|
||||||
|
|
||||||
renderVizPlugin = (plugin: PanelPlugin, index: number) => {
|
|
||||||
const isSelected = this.state.selected === index;
|
|
||||||
const isCurrent = plugin.id === this.props.current.id;
|
const isCurrent = plugin.id === this.props.current.id;
|
||||||
return (
|
return (
|
||||||
<VizTypePickerPlugin
|
<VizTypePickerPlugin
|
||||||
@ -94,9 +60,9 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
isCurrent={isCurrent}
|
isCurrent={isCurrent}
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
this.onMouseEnter(index);
|
onMouseEnter(index);
|
||||||
}}
|
}}
|
||||||
onClick={() => this.props.onTypeChanged(plugin)}
|
onClick={() => onTypeChanged(plugin)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -118,11 +84,11 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
searchQuery: value,
|
searchQuery: value,
|
||||||
selected: 0,
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFilters = () => {
|
renderFilters = ({ onKeyDown, selected }: KeyboardNavigationProps) => {
|
||||||
|
const { searchQuery } = this.state;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label className="gf-form--has-input-icon">
|
<label className="gf-form--has-input-icon">
|
||||||
@ -132,7 +98,14 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
placeholder=""
|
placeholder=""
|
||||||
ref={elem => (this.searchInput = elem)}
|
ref={elem => (this.searchInput = elem)}
|
||||||
onChange={this.onSearchQueryChange}
|
onChange={this.onSearchQueryChange}
|
||||||
onKeyDown={this.onKeyDown}
|
value={searchQuery}
|
||||||
|
onKeyDown={evt => {
|
||||||
|
onKeyDown(evt, this.maxSelectedIndex, () => {
|
||||||
|
const { onTypeChanged } = this.props;
|
||||||
|
const vizType = this.getFilteredPluginList()[selected];
|
||||||
|
onTypeChanged(vizType);
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<i className="gf-form-input-icon fa fa-search" />
|
<i className="gf-form-input-icon fa fa-search" />
|
||||||
</label>
|
</label>
|
||||||
@ -144,13 +117,19 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
|||||||
const filteredPluginList = this.getFilteredPluginList();
|
const filteredPluginList = this.getFilteredPluginList();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<KeyboardNavigation
|
||||||
<div className="cta-form__bar">
|
render={(keyNavProps: KeyboardNavigationProps) => (
|
||||||
{this.renderFilters()}
|
<>
|
||||||
<div className="gf-form--grow" />
|
<div className="cta-form__bar">
|
||||||
</div>
|
{this.renderFilters(keyNavProps)}
|
||||||
<div className="viz-picker">{filteredPluginList.map(this.renderVizPlugin)}</div>
|
<div className="gf-form--grow" />
|
||||||
</>
|
</div>
|
||||||
|
<div className="viz-picker">
|
||||||
|
{filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index, keyNavProps))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Props } from './DataSourcePicker';
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
selected: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const withKeyboardNavigation = WrappedComponent => {
|
|
||||||
return class extends React.Component<Props, State> {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
selected: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
goToNext = (maxSelectedIndex: number) => {
|
|
||||||
const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1;
|
|
||||||
this.setState({
|
|
||||||
selected: nextIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
goToPrev = (maxSelectedIndex: number) => {
|
|
||||||
const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1;
|
|
||||||
this.setState({
|
|
||||||
selected: nextIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => {
|
|
||||||
if (evt.key === 'ArrowDown') {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.goToNext(maxSelectedIndex);
|
|
||||||
}
|
|
||||||
if (evt.key === 'ArrowUp') {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.goToPrev(maxSelectedIndex);
|
|
||||||
}
|
|
||||||
if (evt.key === 'Enter' && onEnterAction) {
|
|
||||||
onEnterAction();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMouseEnter = (mouseEnterIndex: number) => {
|
|
||||||
this.setState({
|
|
||||||
selected: mouseEnterIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<WrappedComponent
|
|
||||||
selected={this.state.selected}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
onMouseEnter={this.onMouseEnter}
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withKeyboardNavigation;
|
|
Loading…
Reference in New Issue
Block a user