1// @ts-ignore
2import baron from 'baron';
3import { PanelEvents } from '@grafana/data';
4import { PanelModel } from '../../features/dashboard/state';
5import { PanelCtrl } from './panel_ctrl';
6import { Subscription } from 'rxjs';
7import { PanelDirectiveReadyEvent, RenderEvent } from 'app/types/events';
8import { coreModule } from 'app/angular/core_module';
9import { RefreshEvent } from '@grafana/runtime';
10
11const panelTemplate = `
12  <ng-transclude class="panel-height-helper"></ng-transclude>
13`;
14
15coreModule.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
16  return {
17    restrict: 'E',
18    template: panelTemplate,
19    transclude: true,
20    scope: { ctrl: '=' },
21    link: (scope: any, elem) => {
22      const ctrl: PanelCtrl = scope.ctrl;
23      const panel: PanelModel = scope.ctrl.panel;
24      const subs = new Subscription();
25
26      let panelScrollbar: any;
27
28      function resizeScrollableContent() {
29        if (panelScrollbar) {
30          panelScrollbar.update();
31        }
32      }
33
34      ctrl.events.on(PanelEvents.componentDidMount, () => {
35        if ((ctrl as any).__proto__.constructor.scrollable) {
36          const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable';
37          const scrollerClass = 'baron__scroller';
38          const scrollBarHTML = `
39            <div class="baron__track">
40              <div class="baron__bar"></div>
41            </div>
42          `;
43
44          const scrollRoot = elem;
45          const scroller = elem.find(':first').find(':first');
46
47          scrollRoot.addClass(scrollRootClass);
48          $(scrollBarHTML).appendTo(scrollRoot);
49          scroller.addClass(scrollerClass);
50
51          panelScrollbar = baron({
52            root: scrollRoot[0],
53            scroller: scroller[0],
54            bar: '.baron__bar',
55            barOnCls: '_scrollbar',
56            scrollingCls: '_scrolling',
57          });
58
59          panelScrollbar.scroll();
60        }
61      });
62
63      function updateDimensionsFromParentScope() {
64        ctrl.height = scope.$parent.$parent.size.height;
65        ctrl.width = scope.$parent.$parent.size.width;
66      }
67
68      updateDimensionsFromParentScope();
69
70      // Pass PanelModel events down to angular controller event emitter
71      subs.add(
72        panel.events.subscribe(RefreshEvent, () => {
73          updateDimensionsFromParentScope();
74          ctrl.events.emit('refresh');
75        })
76      );
77
78      subs.add(
79        panel.events.subscribe(RenderEvent, (event) => {
80          // this event originated from angular so no need to bubble it back
81          if (event.payload?.fromAngular) {
82            return;
83          }
84
85          updateDimensionsFromParentScope();
86
87          $timeout(() => {
88            resizeScrollableContent();
89            ctrl.events.emit('render');
90          });
91        })
92      );
93
94      subs.add(
95        ctrl.events.subscribe(RenderEvent, (event) => {
96          // this event originated from angular so bubble it to react so the PanelChromeAngular can update the panel header alert state
97          if (event.payload) {
98            event.payload.fromAngular = true;
99            panel.events.publish(event);
100          }
101        })
102      );
103
104      scope.$on('$destroy', () => {
105        elem.off();
106
107        // Remove PanelModel.event subs
108        subs.unsubscribe();
109        // Remove Angular controller event subs
110        ctrl.events.emit(PanelEvents.panelTeardown);
111        ctrl.events.removeAllListeners();
112
113        if (panelScrollbar) {
114          panelScrollbar.dispose();
115        }
116      });
117
118      panel.events.publish(PanelDirectiveReadyEvent);
119    },
120  };
121});
122