Commit 5975c4a7 by Torkel Ödegaard

ux: changed panel selection ux

parent b372b432
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 ...@@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
const transitionStyles = { const transitionStyles = {
exited: { maxHeight: 0 }, exited: { maxHeight: 0 },
entering: { maxHeight: maxHeight }, entering: { maxHeight: maxHeight },
entered: { maxHeight: maxHeight, overflow: 'visible' }, entered: { maxHeight: 'unset', overflow: 'visible' },
exiting: { maxHeight: 0 }, exiting: { maxHeight: 0 },
}; };
......
import React from 'react'; import React from 'react';
import config from 'app/core/config'; 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 { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { DashboardRow } from './DashboardRow';
import { AddPanelPanel } from './AddPanelPanel';
import { importPluginModule } from 'app/features/plugins/plugin_loader'; 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 { PanelChrome } from './PanelChrome';
import { PanelEditor } from './PanelEditor'; import { PanelEditor } from './PanelEditor';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
export interface Props { export interface Props {
panelType: string; panelType: string;
panel: PanelModel; panel: PanelModel;
...@@ -17,20 +20,19 @@ export interface Props { ...@@ -17,20 +20,19 @@ export interface Props {
} }
export interface State { export interface State {
pluginExports: PluginExports; plugin: PanelPlugin;
} }
export class DashboardPanel extends React.Component<Props, State> { export class DashboardPanel extends React.Component<Props, State> {
element: any; element: any;
angularPanel: AngularComponent; angularPanel: AngularComponent;
pluginInfo: any;
specialPanels = {}; specialPanels = {};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
pluginExports: null, plugin: null,
}; };
this.specialPanels['row'] = this.renderRow.bind(this); this.specialPanels['row'] = this.renderRow.bind(this);
...@@ -63,20 +65,22 @@ export class DashboardPanel extends React.Component<Props, State> { ...@@ -63,20 +65,22 @@ export class DashboardPanel extends React.Component<Props, State> {
return; return;
} }
const { panel } = this.props;
// handle plugin loading & changing of plugin type // handle plugin loading & changing of plugin type
if (!this.pluginInfo || this.pluginInfo.id !== this.props.panel.type) { if (!this.state.plugin || this.state.plugin.id !== panel.type) {
this.pluginInfo = config.panels[this.props.panel.type]; const plugin = config.panels[panel.type];
if (this.pluginInfo.exports) { if (plugin.exports) {
this.cleanUpAngularPanel(); this.cleanUpAngularPanel();
this.setState({ pluginExports: this.pluginInfo.exports }); this.setState({ plugin: plugin });
} else { } else {
importPluginModule(this.pluginInfo.module).then(pluginExports => { importPluginModule(plugin.module).then(pluginExports => {
this.cleanUpAngularPanel(); this.cleanUpAngularPanel();
// cache plugin exports (saves a promise async cycle next time) // cache plugin exports (saves a promise async cycle next time)
this.pluginInfo.exports = pluginExports; plugin.exports = pluginExports;
// update panel state // update panel state
this.setState({ pluginExports: pluginExports }); this.setState({ plugin: plugin });
}); });
} }
} }
...@@ -112,44 +116,41 @@ export class DashboardPanel extends React.Component<Props, State> { ...@@ -112,44 +116,41 @@ export class DashboardPanel extends React.Component<Props, State> {
} }
renderReactPanel() { renderReactPanel() {
const { pluginExports } = this.state; const { dashboard, panel } = this.props;
const containerClass = this.props.panel.isEditing ? 'panel-editor-container' : 'panel-height-helper'; const { plugin } = this.state;
const panelWrapperClass = this.props.panel.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
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 // 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 // I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel
return ( return (
<div className={containerClass}> <div className={containerClass}>
<div className={panelWrapperClass}> <div className={panelWrapperClass}>
<PanelChrome <PanelChrome component={plugin.exports.PanelComponent} panel={panel} dashboard={dashboard} />
component={pluginExports.PanelComponent}
panel={this.props.panel}
dashboard={this.props.dashboard}
/>
</div> </div>
{this.props.panel.isEditing && ( {panel.isEditing && (
<PanelEditor <PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
panel={this.props.panel}
panelType={this.props.panel.type}
dashboard={this.props.dashboard}
onTypeChanged={this.onPluginTypeChanged}
pluginExports={pluginExports}
/>
)} )}
</div> </div>
); );
} }
render() { render() {
const { panel } = this.props;
const { plugin } = this.state;
if (this.isSpecial()) { 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; return null;
} }
if (this.state.pluginExports.PanelComponent) { // if exporting PanelComponent it must be a react panel
if (plugin.exports.PanelComponent) {
return this.renderReactPanel(); return this.renderReactPanel();
} }
......
...@@ -10,13 +10,12 @@ import { updateLocation } from 'app/core/actions'; ...@@ -10,13 +10,12 @@ import { updateLocation } from 'app/core/actions';
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model'; import { DashboardModel } from '../dashboard_model';
import { PanelPlugin, PluginExports } from 'app/types/plugins'; import { PanelPlugin } from 'app/types/plugins';
interface PanelEditorProps { interface PanelEditorProps {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
panelType: string; plugin: PanelPlugin;
pluginExports: PluginExports;
onTypeChanged: (newType: PanelPlugin) => void; onTypeChanged: (newType: PanelPlugin) => void;
} }
...@@ -44,11 +43,11 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -44,11 +43,11 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
} }
renderPanelOptions() { renderPanelOptions() {
const { pluginExports, panel } = this.props; const { plugin, panel } = this.props;
const { PanelOptionsComponent } = plugin.exports;
if (pluginExports.PanelOptionsComponent) { if (PanelOptionsComponent) {
const OptionsComponent = pluginExports.PanelOptionsComponent; return <PanelOptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
return <OptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
} else { } else {
return <p>Visualization has no options</p>; return <p>Visualization has no options</p>;
} }
...@@ -62,7 +61,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -62,7 +61,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
renderVizTab() { renderVizTab() {
return ( return (
<div className="viz-editor"> <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()} {this.renderPanelOptions()}
</div> </div>
); );
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash';
import { FadeIn } from 'app/core/components/Animations/FadeIn';
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 CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
import _ from 'lodash';
interface Props { interface Props {
currentType: string; current: PanelPlugin;
onTypeChanged: (newType: PanelPlugin) => void; onTypeChanged: (newType: PanelPlugin) => void;
} }
interface State { interface State {
pluginList: PanelPlugin[]; pluginList: PanelPlugin[];
isOpen: boolean;
} }
export class VizTypePicker extends PureComponent<Props, State> { export class VizTypePicker extends PureComponent<Props, State> {
...@@ -21,6 +22,7 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -21,6 +22,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
this.state = { this.state = {
pluginList: this.getPanelPlugins(''), pluginList: this.getPanelPlugins(''),
isOpen: false,
}; };
} }
...@@ -37,7 +39,7 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -37,7 +39,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
renderVizPlugin = (plugin, index) => { renderVizPlugin = (plugin, index) => {
const cssClass = classNames({ const cssClass = classNames({
'viz-picker__item': true, 'viz-picker__item': true,
'viz-picker__item--selected': plugin.id === this.props.currentType, 'viz-picker__item--selected': plugin.id === this.props.current.id,
}); });
return ( return (
...@@ -55,7 +57,7 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -55,7 +57,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
<input type="text" className="gf-form-input width-13" placeholder="" /> <input type="text" className="gf-form-input width-13" placeholder="" />
<i className="gf-form-input-icon fa fa-search" /> <i className="gf-form-input-icon fa fa-search" />
</label> </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 active">Basic Types</button>
<button className="btn toggle-btn gf-form-btn">Master Types</button> <button className="btn toggle-btn gf-form-btn">Master Types</button>
</div> </div>
...@@ -63,24 +65,52 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -63,24 +65,52 @@ export class VizTypePicker extends PureComponent<Props, State> {
); );
} }
onToggleOpen = () => {
this.setState({ isOpen: !this.state.isOpen });
};
render() { render() {
const { currentType } = this.props; const { current } = this.props;
const { pluginList } = this.state; const { pluginList, isOpen } = this.state;
return ( return (
<div className="viz-picker"> <div className="viz-picker">
<div className="viz-picker__bar"> <div className="viz-picker__bar">
<label className="gf-form-label">Visualization</label> <div className="gf-form-inline">
<label className="gf-form-input width-10"> <div className="gf-form">
<span>{currentType}</span> <label className="gf-form-label">Visualization</label>
</label> <label className="gf-form-input width-10" onClick={this.onToggleOpen}>
<div className="gf-form--grow" /> <span>{current.name}</span>
{this.renderFilters()} {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> </div>
<CustomScrollbar> <FadeIn in={isOpen} duration={300}>
<div className="viz-picker__items">{pluginList.map(this.renderVizPlugin)}</div> <div className="cta-form">
</CustomScrollbar> <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> </div>
); );
} }
......
...@@ -410,7 +410,27 @@ select.gf-form-input ~ .gf-form-help-icon { ...@@ -410,7 +410,27 @@ select.gf-form-input ~ .gf-form-help-icon {
} }
.cta-form__close { .cta-form__close {
background: transparent;
padding: 4px 8px 4px 9px;
border: none;
position: absolute; position: absolute;
right: 0; 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 @@ ...@@ -59,6 +59,7 @@
.viz-picker__items { .viz-picker__items {
display: flex; display: flex;
flex-wrap: wrap;
// for scrollbar // for scrollbar
margin-bottom: 13px; margin-bottom: 13px;
} }
...@@ -68,14 +69,15 @@ ...@@ -68,14 +69,15 @@
box-shadow: $card-shadow; box-shadow: $card-shadow;
border-radius: 3px; border-radius: 3px;
height: 70px; height: 90px;
width: 130px; width: 150px;
flex-shrink: 0; flex-shrink: 0;
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
margin-right: 6px; margin-right: 10px;
margin-bottom: 10px;
border: 1px solid transparent; border: 1px solid transparent;
align-items: center; align-items: center;
...@@ -98,11 +100,11 @@ ...@@ -98,11 +100,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: center; align-self: center;
height: 20px; height: 23px;
} }
.viz-picker__item-img { .viz-picker__item-img {
height: 40px; height: 55px;
} }
.panel-editor__aside { .panel-editor__aside {
...@@ -162,7 +164,6 @@ ...@@ -162,7 +164,6 @@
} }
.viz-picker__bar { .viz-picker__bar {
display: flex;
margin-bottom: $spacer; margin-bottom: $spacer;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment