Procházet zdrojové kódy

poc: handling panel edit mode in react even for angular panels poc

Torkel Ödegaard před 7 roky
rodič
revize
911646f913

+ 4 - 0
public/app/core/services/AngularLoader.ts

@@ -5,6 +5,7 @@ import _ from 'lodash';
 export interface AngularComponent {
   destroy();
   digest();
+  getScope();
 }
 
 export class AngularLoader {
@@ -28,6 +29,9 @@ export class AngularLoader {
       digest: () => {
         scope.$digest();
       },
+      getScope: () => {
+        return scope;
+      },
     };
   }
 }

+ 41 - 36
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -23,11 +23,11 @@ export interface Props {
 
 export interface State {
   plugin: PanelPlugin;
+  angularPanel: AngularComponent;
 }
 
 export class DashboardPanel extends PureComponent<Props, State> {
-  element: any;
-  angularPanel: AngularComponent;
+  element: HTMLElement;
   specialPanels = {};
 
   constructor(props) {
@@ -35,6 +35,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
 
     this.state = {
       plugin: null,
+      angularPanel: null,
     };
 
     this.specialPanels['row'] = this.renderRow.bind(this);
@@ -96,25 +97,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
     this.loadPlugin();
 
     // handle angular plugin loading
-    if (!this.element || this.angularPanel) {
+    if (!this.element || this.state.angularPanel) {
       return;
     }
 
     const loader = getAngularLoader();
     const template = '<plugin-component type="panel" class="panel-height-helper"></plugin-component>';
     const scopeProps = { panel: this.props.panel, dashboard: this.props.dashboard };
-    this.angularPanel = loader.load(this.element, scopeProps, template);
+    const angularPanel = loader.load(this.element, scopeProps, template);
+
+    this.setState({ angularPanel });
   }
 
-  cleanUpAngularPanel() {
-    if (this.angularPanel) {
-      this.angularPanel.destroy();
-      this.angularPanel = null;
+  cleanUpAngularPanel(unmounted?: boolean) {
+    if (this.state.angularPanel) {
+      this.state.angularPanel.destroy();
+
+      if (!unmounted) {
+        this.setState({ angularPanel: null });
+      }
     }
   }
 
   componentWillUnmount() {
-    this.cleanUpAngularPanel();
+    this.cleanUpAngularPanel(true);
   }
 
   onMouseEnter = () => {
@@ -129,25 +135,16 @@ export class DashboardPanel extends PureComponent<Props, State> {
     const { dashboard, panel } = this.props;
     const { plugin } = this.state;
 
-    const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
-    const panelWrapperClass = this.props.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} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
-          <PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />
-        </div>
-        {panel.isEditing && (
-          <PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
-        )}
-      </div>
-    );
+    return <PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />;
+  }
+
+  renderAngularPanel() {
+    return <div ref={element => (this.element = element)} className="panel-height-helper" />;
   }
 
   render() {
-    const { panel } = this.props;
-    const { plugin } = this.state;
+    const { panel, dashboard } = this.props;
+    const { plugin, angularPanel } = this.state;
 
     if (this.isSpecial()) {
       return this.specialPanels[panel.type]();
@@ -158,19 +155,27 @@ export class DashboardPanel extends PureComponent<Props, State> {
       return null;
     }
 
-    // if exporting PanelComponent it must be a react panel
-    if (plugin.exports.Panel) {
-      return this.renderReactPanel();
-    }
+    console.log('DashboardPanel.render()');
+
+    const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
+    const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
 
-    // legacy angular rendering
     return (
-      <div
-        ref={element => (this.element = element)}
-        className="panel-height-helper"
-        onMouseEnter={this.onMouseEnter}
-        onMouseLeave={this.onMouseLeave}
-      />
+      <div className={containerClass}>
+        <div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
+          {plugin.exports.Panel && this.renderReactPanel()}
+          {plugin.exports.PanelCtrl && this.renderAngularPanel()}
+        </div>
+        {panel.isEditing && (
+          <PanelEditor
+            panel={panel}
+            plugin={plugin}
+            dashboard={dashboard}
+            angularPanel={angularPanel}
+            onTypeChanged={this.onPluginTypeChanged}
+          />
+        )}
+      </div>
     );
   }
 }

+ 10 - 2
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -7,6 +7,7 @@ import { GeneralTab } from './GeneralTab';
 
 import { store } from 'app/store/store';
 import { updateLocation } from 'app/core/actions';
+import { AngularComponent } from 'app/core/services/AngularLoader';
 
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
@@ -16,6 +17,7 @@ interface PanelEditorProps {
   panel: PanelModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
+  angularPanel?: AngularComponent;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 
@@ -58,7 +60,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
   };
 
   render() {
-    const { panel, dashboard, onTypeChanged, plugin } = this.props;
+    const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props;
     const { location } = store.getState();
     const activeTab = location.query.tab || 'queries';
 
@@ -85,7 +87,13 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
         {activeTab === 'general' && <GeneralTab panel={panel} />}
         {activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
         {activeTab === 'visualization' && (
-          <VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
+          <VisualizationTab
+            panel={panel}
+            dashboard={dashboard}
+            plugin={plugin}
+            onTypeChanged={onTypeChanged}
+            angularPanel={angularPanel}
+          />
         )}
       </div>
     );

+ 36 - 1
public/app/features/dashboard/dashgrid/VisualizationTab.tsx

@@ -1,8 +1,14 @@
+// Libraries
 import React, { PureComponent } from 'react';
 
+// Utils & Services
+import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+
+// Components
 import { EditorTabBody } from './EditorTabBody';
 import { VizTypePicker } from './VizTypePicker';
 
+// Types
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
@@ -11,18 +17,26 @@ interface Props {
   panel: PanelModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
+  angularPanel?: AngularComponent;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 
 export class VisualizationTab extends PureComponent<Props> {
+  element: HTMLElement;
+  angularOptions: AngularComponent;
+
   constructor(props) {
     super(props);
   }
 
   renderPanelOptions() {
-    const { plugin, panel } = this.props;
+    const { plugin, panel, angularPanel } = this.props;
     const { PanelOptions } = plugin.exports;
 
+    if (angularPanel) {
+      return <div ref={element => (this.element = element)} />;
+    }
+
     if (PanelOptions) {
       return <PanelOptions options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
     } else {
@@ -30,6 +44,27 @@ export class VisualizationTab extends PureComponent<Props> {
     }
   }
 
+  componentDidMount() {
+    const { angularPanel } = this.props;
+
+    if (angularPanel) {
+      const scope = angularPanel.getScope();
+      const panelCtrl = scope.$$childHead.ctrl;
+
+      const loader = getAngularLoader();
+      const template = '<panel-editor-tab editor-tab="tab" ctrl="ctrl"></panel-editor-tab>';
+      const scopeProps = { ctrl: panelCtrl, tab: panelCtrl.editorTabs[2] };
+
+      this.angularOptions = loader.load(this.element, scopeProps, template);
+    }
+  }
+
+  componentWillUnmount() {
+    if (this.angularOptions) {
+      this.angularOptions.destroy();
+    }
+  }
+
   onPanelOptionsChanged = (options: any) => {
     this.props.panel.updateOptions(options);
     this.forceUpdate();

+ 14 - 46
public/app/features/panel/panel_directive.ts

@@ -6,54 +6,22 @@ import baron from 'baron';
 const module = angular.module('grafana.directives');
 
 const panelTemplate = `
-  <div ng-class="{'panel-editor-container': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
-    <div ng-class="{'panel-editor-container__panel': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
-      <div class="panel-container">
-        <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
-          <span class="panel-info-corner">
-            <i class="fa"></i>
-            <span class="panel-info-corner-inner"></span>
-          </span>
-
-          <span class="panel-loading" ng-show="ctrl.loading">
-            <i class="fa fa-spinner fa-spin"></i>
-          </span>
-
-          <panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
-        </div>
-
-        <div class="panel-content">
-          <ng-transclude class="panel-height-helper"></ng-transclude>
-        </div>
+  <div class="panel-container">
+      <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
+        <span class="panel-info-corner">
+          <i class="fa"></i>
+          <span class="panel-info-corner-inner"></span>
+        </span>
+
+        <span class="panel-loading" ng-show="ctrl.loading">
+          <i class="fa fa-spinner fa-spin"></i>
+        </span>
+
+        <panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
       </div>
-    </div>
 
-    <div ng-if="ctrl.panel.isEditing" ng-class="{'panel-editor-container__editor': ctrl.panel.isEditing,
-                                                 'panel-height-helper': !ctrl.panel.isEditing}">
-      <div class="tabbed-view tabbed-view--new">
-        <div class="tabbed-view-header">
-          <h3 class="tabbed-view-panel-title">
-            {{ctrl.pluginName}}
-          </h3>
-
-          <ul class="gf-tabs">
-            <li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
-              <a class="gf-tabs-link" ng-click="ctrl.changeTab($index)" ng-class="{active: ctrl.editorTabIndex === $index}">
-                {{::tab.title}}
-              </a>
-            </li>
-          </ul>
-
-          <button class="panel-editor-tabs__close" ng-click="ctrl.exitFullscreen();">
-            <i class="fa fa-reply"></i>
-          </button>
-        </div>
-
-        <div class="tabbed-view-body">
-          <div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index" class="panel-height-helper">
-            <panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
-          </div>
-        </div>
+      <div class="panel-content">
+        <ng-transclude class="panel-height-helper"></ng-transclude>
       </div>
     </div>
   </div>

+ 0 - 1
public/app/features/panel/panel_editor_tab.ts

@@ -9,7 +9,6 @@ function panelEditorTab(dynamicDirectiveSrv) {
     scope: {
       ctrl: '=',
       editorTab: '=',
-      index: '=',
     },
     directive: scope => {
       const pluginId = scope.ctrl.pluginId;