mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ux: changed panel selection ux
This commit is contained in:
parent
b372b4323d
commit
5975c4a737
37
public/app/core/components/Animations/FadeIn.tsx
Normal file
37
public/app/core/components/Animations/FadeIn.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { SFC } from 'react';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
|
||||
interface Props {
|
||||
duration: number;
|
||||
children: JSX.Element;
|
||||
in: boolean;
|
||||
}
|
||||
|
||||
export const FadeIn: SFC<Props> = props => {
|
||||
const defaultStyle = {
|
||||
transition: `opacity ${props.duration}ms linear`,
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
const transitionStyles = {
|
||||
exited: { opacity: 0, display: 'none' },
|
||||
entering: { opacity: 0 },
|
||||
entered: { opacity: 1 },
|
||||
exiting: { opacity: 0 },
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition in={props.in} timeout={props.duration}>
|
||||
{state => (
|
||||
<div
|
||||
style={{
|
||||
...defaultStyle,
|
||||
...transitionStyles[state],
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
|
||||
const transitionStyles = {
|
||||
exited: { maxHeight: 0 },
|
||||
entering: { maxHeight: maxHeight },
|
||||
entered: { maxHeight: maxHeight, overflow: 'visible' },
|
||||
entered: { maxHeight: 'unset', overflow: 'visible' },
|
||||
exiting: { maxHeight: 0 },
|
||||
};
|
||||
|
||||
|
@ -1,15 +1,18 @@
|
||||
import React from 'react';
|
||||
import config from 'app/core/config';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
|
||||
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
|
||||
import { DashboardRow } from './DashboardRow';
|
||||
import { AddPanelPanel } from './AddPanelPanel';
|
||||
import { importPluginModule } from 'app/features/plugins/plugin_loader';
|
||||
import { PluginExports, PanelPlugin } from 'app/types/plugins';
|
||||
|
||||
import { AddPanelPanel } from './AddPanelPanel';
|
||||
import { DashboardRow } from './DashboardRow';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
import { PanelChrome } from './PanelChrome';
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
|
||||
export interface Props {
|
||||
panelType: string;
|
||||
panel: PanelModel;
|
||||
@ -17,20 +20,19 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface State {
|
||||
pluginExports: PluginExports;
|
||||
plugin: PanelPlugin;
|
||||
}
|
||||
|
||||
export class DashboardPanel extends React.Component<Props, State> {
|
||||
element: any;
|
||||
angularPanel: AngularComponent;
|
||||
pluginInfo: any;
|
||||
specialPanels = {};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pluginExports: null,
|
||||
plugin: null,
|
||||
};
|
||||
|
||||
this.specialPanels['row'] = this.renderRow.bind(this);
|
||||
@ -63,20 +65,22 @@ export class DashboardPanel extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
// handle plugin loading & changing of plugin type
|
||||
if (!this.pluginInfo || this.pluginInfo.id !== this.props.panel.type) {
|
||||
this.pluginInfo = config.panels[this.props.panel.type];
|
||||
const { panel } = this.props;
|
||||
|
||||
if (this.pluginInfo.exports) {
|
||||
// handle plugin loading & changing of plugin type
|
||||
if (!this.state.plugin || this.state.plugin.id !== panel.type) {
|
||||
const plugin = config.panels[panel.type];
|
||||
|
||||
if (plugin.exports) {
|
||||
this.cleanUpAngularPanel();
|
||||
this.setState({ pluginExports: this.pluginInfo.exports });
|
||||
this.setState({ plugin: plugin });
|
||||
} else {
|
||||
importPluginModule(this.pluginInfo.module).then(pluginExports => {
|
||||
importPluginModule(plugin.module).then(pluginExports => {
|
||||
this.cleanUpAngularPanel();
|
||||
// cache plugin exports (saves a promise async cycle next time)
|
||||
this.pluginInfo.exports = pluginExports;
|
||||
plugin.exports = pluginExports;
|
||||
// update panel state
|
||||
this.setState({ pluginExports: pluginExports });
|
||||
this.setState({ plugin: plugin });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -112,44 +116,41 @@ export class DashboardPanel extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
renderReactPanel() {
|
||||
const { pluginExports } = this.state;
|
||||
const containerClass = this.props.panel.isEditing ? 'panel-editor-container' : 'panel-height-helper';
|
||||
const panelWrapperClass = this.props.panel.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
|
||||
const { dashboard, panel } = this.props;
|
||||
const { plugin } = this.state;
|
||||
|
||||
const containerClass = panel.isEditing ? 'panel-editor-container' : 'panel-height-helper';
|
||||
const panelWrapperClass = panel.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
|
||||
|
||||
// this might look strange with these classes that change when edit, but
|
||||
// I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<div className={panelWrapperClass}>
|
||||
<PanelChrome
|
||||
component={pluginExports.PanelComponent}
|
||||
panel={this.props.panel}
|
||||
dashboard={this.props.dashboard}
|
||||
/>
|
||||
<PanelChrome component={plugin.exports.PanelComponent} panel={panel} dashboard={dashboard} />
|
||||
</div>
|
||||
{this.props.panel.isEditing && (
|
||||
<PanelEditor
|
||||
panel={this.props.panel}
|
||||
panelType={this.props.panel.type}
|
||||
dashboard={this.props.dashboard}
|
||||
onTypeChanged={this.onPluginTypeChanged}
|
||||
pluginExports={pluginExports}
|
||||
/>
|
||||
{panel.isEditing && (
|
||||
<PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panel } = this.props;
|
||||
const { plugin } = this.state;
|
||||
|
||||
if (this.isSpecial()) {
|
||||
return this.specialPanels[this.props.panel.type]();
|
||||
return this.specialPanels[panel.type]();
|
||||
}
|
||||
|
||||
if (!this.state.pluginExports) {
|
||||
// if we have not loaded plugin exports yet, wait
|
||||
if (!plugin || !plugin.exports) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.pluginExports.PanelComponent) {
|
||||
// if exporting PanelComponent it must be a react panel
|
||||
if (plugin.exports.PanelComponent) {
|
||||
return this.renderReactPanel();
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,12 @@ import { updateLocation } from 'app/core/actions';
|
||||
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { PanelPlugin, PluginExports } from 'app/types/plugins';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
|
||||
interface PanelEditorProps {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
panelType: string;
|
||||
pluginExports: PluginExports;
|
||||
plugin: PanelPlugin;
|
||||
onTypeChanged: (newType: PanelPlugin) => void;
|
||||
}
|
||||
|
||||
@ -44,11 +43,11 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
}
|
||||
|
||||
renderPanelOptions() {
|
||||
const { pluginExports, panel } = this.props;
|
||||
const { plugin, panel } = this.props;
|
||||
const { PanelOptionsComponent } = plugin.exports;
|
||||
|
||||
if (pluginExports.PanelOptionsComponent) {
|
||||
const OptionsComponent = pluginExports.PanelOptionsComponent;
|
||||
return <OptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
|
||||
if (PanelOptionsComponent) {
|
||||
return <PanelOptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
|
||||
} else {
|
||||
return <p>Visualization has no options</p>;
|
||||
}
|
||||
@ -62,7 +61,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
renderVizTab() {
|
||||
return (
|
||||
<div className="viz-editor">
|
||||
<VizTypePicker currentType={this.props.panel.type} onTypeChanged={this.props.onTypeChanged} />
|
||||
<VizTypePicker current={this.props.plugin} onTypeChanged={this.props.onTypeChanged} />
|
||||
{this.renderPanelOptions()}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,18 +1,19 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||
import config from 'app/core/config';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
|
||||
interface Props {
|
||||
currentType: string;
|
||||
current: PanelPlugin;
|
||||
onTypeChanged: (newType: PanelPlugin) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
pluginList: PanelPlugin[];
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export class VizTypePicker extends PureComponent<Props, State> {
|
||||
@ -21,6 +22,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
||||
|
||||
this.state = {
|
||||
pluginList: this.getPanelPlugins(''),
|
||||
isOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -37,7 +39,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
||||
renderVizPlugin = (plugin, index) => {
|
||||
const cssClass = classNames({
|
||||
'viz-picker__item': true,
|
||||
'viz-picker__item--selected': plugin.id === this.props.currentType,
|
||||
'viz-picker__item--selected': plugin.id === this.props.current.id,
|
||||
});
|
||||
|
||||
return (
|
||||
@ -55,7 +57,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
||||
<input type="text" className="gf-form-input width-13" placeholder="" />
|
||||
<i className="gf-form-input-icon fa fa-search" />
|
||||
</label>
|
||||
<div>
|
||||
<div className="p-l-1">
|
||||
<button className="btn toggle-btn gf-form-btn active">Basic Types</button>
|
||||
<button className="btn toggle-btn gf-form-btn">Master Types</button>
|
||||
</div>
|
||||
@ -63,24 +65,52 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
onToggleOpen = () => {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentType } = this.props;
|
||||
const { pluginList } = this.state;
|
||||
const { current } = this.props;
|
||||
const { pluginList, isOpen } = this.state;
|
||||
|
||||
return (
|
||||
<div className="viz-picker">
|
||||
<div className="viz-picker__bar">
|
||||
<label className="gf-form-label">Visualization</label>
|
||||
<label className="gf-form-input width-10">
|
||||
<span>{currentType}</span>
|
||||
</label>
|
||||
<div className="gf-form--grow" />
|
||||
{this.renderFilters()}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label">Visualization</label>
|
||||
<label className="gf-form-input width-10" onClick={this.onToggleOpen}>
|
||||
<span>{current.name}</span>
|
||||
{isOpen && <i className="fa fa-caret-down pull-right" />}
|
||||
{!isOpen && <i className="fa fa-caret-left pull-right" />}
|
||||
</label>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label">
|
||||
<i className="fa fa-caret-down" /> Help
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomScrollbar>
|
||||
<div className="viz-picker__items">{pluginList.map(this.renderVizPlugin)}</div>
|
||||
</CustomScrollbar>
|
||||
<FadeIn in={isOpen} duration={300}>
|
||||
<div className="cta-form">
|
||||
<button className="cta-form__close" onClick={this.onToggleOpen}>
|
||||
<i className="fa fa-remove" />
|
||||
</button>
|
||||
|
||||
<div className="cta-form__bar">
|
||||
<div className="cta-form__bar-header">Select visualization</div>
|
||||
{this.renderFilters()}
|
||||
<div className="gf-form--grow" />
|
||||
</div>
|
||||
|
||||
<div className="viz-picker__items">{pluginList.map(this.renderVizPlugin)}</div>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -410,7 +410,27 @@ select.gf-form-input ~ .gf-form-help-icon {
|
||||
}
|
||||
|
||||
.cta-form__close {
|
||||
background: transparent;
|
||||
padding: 4px 8px 4px 9px;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
top: -2px;
|
||||
font-size: $font-size-lg;
|
||||
|
||||
&:hover {
|
||||
color: $text-color-strong;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-form__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cta-form__bar-header {
|
||||
font-size: $font-size-h4;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
@ -59,6 +59,7 @@
|
||||
|
||||
.viz-picker__items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
// for scrollbar
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
@ -68,14 +69,15 @@
|
||||
box-shadow: $card-shadow;
|
||||
|
||||
border-radius: 3px;
|
||||
height: 70px;
|
||||
width: 130px;
|
||||
height: 90px;
|
||||
width: 150px;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
margin-right: 6px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid transparent;
|
||||
align-items: center;
|
||||
|
||||
@ -98,11 +100,11 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
height: 20px;
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
.viz-picker__item-img {
|
||||
height: 40px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.panel-editor__aside {
|
||||
@ -162,7 +164,6 @@
|
||||
}
|
||||
|
||||
.viz-picker__bar {
|
||||
display: flex;
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user