1// Libraries 2// eslint-disable-next-line lodash/import-scope 3import _ from 'lodash'; 4import $ from 'jquery'; 5 6// Utils and servies 7import { colors } from '@grafana/ui'; 8import { setLegacyAngularInjector, setAppEvents, setAngularLoader } from '@grafana/runtime'; 9import config from 'app/core/config'; 10import coreModule from 'app/angular/core_module'; 11import appEvents from 'app/core/app_events'; 12import { AngularLoader } from 'app/angular/services/AngularLoader'; 13 14// Types 15import { CoreEvents, AppEventEmitter, AppEventConsumer } from 'app/types'; 16import { UtilSrv } from './services/UtilSrv'; 17import { ContextSrv } from 'app/core/services/context_srv'; 18import { IRootScopeService, IAngularEvent, auto } from 'angular'; 19import { AppEvent } from '@grafana/data'; 20import { initGrafanaLive } from 'app/features/live'; 21 22export type GrafanaRootScope = IRootScopeService & AppEventEmitter & AppEventConsumer & { colors: string[] }; 23 24export class GrafanaCtrl { 25 /** @ngInject */ 26 constructor( 27 $scope: any, 28 utilSrv: UtilSrv, 29 $rootScope: GrafanaRootScope, 30 contextSrv: ContextSrv, 31 angularLoader: AngularLoader, 32 $injector: auto.IInjectorService 33 ) { 34 // make angular loader service available to react components 35 setAngularLoader(angularLoader); 36 setLegacyAngularInjector($injector); 37 setAppEvents(appEvents); 38 39 initGrafanaLive(); 40 41 $scope.init = () => { 42 $scope.contextSrv = contextSrv; 43 $scope.appSubUrl = config.appSubUrl; 44 $scope._ = _; 45 utilSrv.init(); 46 }; 47 48 $rootScope.colors = colors; 49 50 $rootScope.onAppEvent = function <T>( 51 event: AppEvent<T> | string, 52 callback: (event: IAngularEvent, ...args: any[]) => void, 53 localScope?: any 54 ) { 55 let unbind; 56 if (typeof event === 'string') { 57 unbind = $rootScope.$on(event, callback); 58 } else { 59 unbind = $rootScope.$on(event.name, callback); 60 } 61 62 let callerScope = this; 63 if (callerScope.$id === 1 && !localScope) { 64 console.warn('warning rootScope onAppEvent called without localscope'); 65 } 66 if (localScope) { 67 callerScope = localScope; 68 } 69 callerScope.$on('$destroy', unbind); 70 }; 71 72 $rootScope.appEvent = <T>(event: AppEvent<T> | string, payload?: T | any) => { 73 if (typeof event === 'string') { 74 $rootScope.$emit(event, payload); 75 appEvents.emit(event, payload); 76 } else { 77 $rootScope.$emit(event.name, payload); 78 appEvents.emit(event, payload); 79 } 80 }; 81 82 $scope.init(); 83 } 84} 85 86/** @ngInject */ 87export function grafanaAppDirective() { 88 return { 89 restrict: 'E', 90 controller: GrafanaCtrl, 91 link: (scope: IRootScopeService & AppEventEmitter, elem: JQuery) => { 92 const body = $('body'); 93 // see https://github.com/zenorocha/clipboard.js/issues/155 94 $.fn.modal.Constructor.prototype.enforceFocus = () => {}; 95 96 appEvents.on(CoreEvents.toggleSidemenuHidden, () => { 97 body.toggleClass('sidemenu-hidden'); 98 }); 99 100 // handle in active view state class 101 let lastActivity = new Date().getTime(); 102 let activeUser = true; 103 const inActiveTimeLimit = 60 * 5000; 104 105 function checkForInActiveUser() { 106 if (!activeUser) { 107 return; 108 } 109 // only go to activity low mode on dashboard page 110 if (!body.hasClass('page-dashboard')) { 111 return; 112 } 113 114 if (new Date().getTime() - lastActivity > inActiveTimeLimit) { 115 activeUser = false; 116 body.addClass('view-mode--inactive'); 117 } 118 } 119 120 function userActivityDetected() { 121 lastActivity = new Date().getTime(); 122 if (!activeUser) { 123 activeUser = true; 124 body.removeClass('view-mode--inactive'); 125 } 126 } 127 128 // mouse and keyboard is user activity 129 body.mousemove(userActivityDetected); 130 body.keydown(userActivityDetected); 131 // set useCapture = true to catch event here 132 document.addEventListener('wheel', userActivityDetected, { capture: true, passive: true }); 133 // treat tab change as activity 134 document.addEventListener('visibilitychange', userActivityDetected); 135 136 // check every 2 seconds 137 setInterval(checkForInActiveUser, 2000); 138 139 // handle document clicks that should hide things 140 body.click((evt) => { 141 const target = $(evt.target); 142 if (target.parents().length === 0) { 143 return; 144 } 145 146 // ensure dropdown menu doesn't impact on z-index 147 body.find('.dropdown-menu-open').removeClass('dropdown-menu-open'); 148 149 // for stuff that animates, slides out etc, clicking it needs to 150 // hide it right away 151 const clickAutoHide = target.closest('[data-click-hide]'); 152 if (clickAutoHide.length) { 153 const clickAutoHideParent = clickAutoHide.parent(); 154 clickAutoHide.detach(); 155 setTimeout(() => { 156 clickAutoHideParent.append(clickAutoHide); 157 }, 100); 158 } 159 160 // hide popovers 161 const popover = elem.find('.popover'); 162 if (popover.length > 0 && target.parents('.graph-legend').length === 0) { 163 popover.hide(); 164 } 165 }); 166 }, 167 }; 168} 169 170coreModule.directive('grafanaApp', grafanaAppDirective); 171