فهرست منبع

ux: changed panel selection ux

Torkel Ödegaard 7 سال پیش
والد
کامیت
5975c4a737

+ 37 - 0
public/app/core/components/Animations/FadeIn.tsx

@@ -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>
+  );
+};

+ 1 - 1
public/app/core/components/Animations/SlideDown.tsx

@@ -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 },
   };
 

+ 35 - 34
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -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;
     }
 
+    const { panel } = this.props;
+
     // 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];
+    if (!this.state.plugin || this.state.plugin.id !== panel.type) {
+      const plugin = config.panels[panel.type];
 
-      if (this.pluginInfo.exports) {
+      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();
     }
 

+ 7 - 8
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -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>
     );

+ 46 - 16
public/app/features/dashboard/dashgrid/VizTypePicker.tsx

@@ -1,18 +1,19 @@
 import React, { PureComponent } from 'react';
 import classNames from 'classnames';
+import _ from 'lodash';
 
+import { FadeIn } from 'app/core/components/Animations/FadeIn';
 import config from 'app/core/config';
 import { PanelPlugin } from 'app/types/plugins';
-import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
-import _ from 'lodash';
 
 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>
     );
   }

+ 21 - 1
public/sass/components/_gf-form.scss

@@ -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;
 }

+ 7 - 6
public/sass/components/_viz_editor.scss

@@ -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;
 }