mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Angular cleanup: Move directives (#35330)
* angular2react: Remove json_editor_ctrl * angular2react: Move directives * Add to angular dir
This commit is contained in:
29
public/app/angular/array_join.ts
Normal file
29
public/app/angular/array_join.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { isArray } from 'lodash';
|
||||
import coreModule from '../core/core_module';
|
||||
|
||||
export function arrayJoin() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: (scope: any, element: any, attr: any, ngModel: any) => {
|
||||
function split_array(text: string) {
|
||||
return (text || '').split(',');
|
||||
}
|
||||
|
||||
function join_array(text: string) {
|
||||
if (isArray(text)) {
|
||||
return ((text || '') as any).join(',');
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
ngModel.$parsers.push(split_array);
|
||||
ngModel.$formatters.push(join_array);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('arrayJoin', arrayJoin);
|
||||
35
public/app/angular/autofill_event_fix.ts
Normal file
35
public/app/angular/autofill_event_fix.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import coreModule from '../core/core_module';
|
||||
|
||||
/** @ngInject */
|
||||
export function autofillEventFix($compile: any) {
|
||||
return {
|
||||
link: ($scope: any, elem: any) => {
|
||||
const input = elem[0];
|
||||
const dispatchChangeEvent = () => {
|
||||
const event = new Event('change');
|
||||
return input.dispatchEvent(event);
|
||||
};
|
||||
const onAnimationStart = ({ animationName }: AnimationEvent) => {
|
||||
switch (animationName) {
|
||||
case 'onAutoFillStart':
|
||||
return dispatchChangeEvent();
|
||||
case 'onAutoFillCancel':
|
||||
return dispatchChangeEvent();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// const onChange = (evt: Event) => console.log(evt);
|
||||
|
||||
input.addEventListener('animationstart', onAnimationStart);
|
||||
// input.addEventListener('change', onChange);
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
input.removeEventListener('animationstart', onAnimationStart);
|
||||
// input.removeEventListener('change', onChange);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('autofillEventFix', autofillEventFix);
|
||||
72
public/app/angular/diff-view.ts
Normal file
72
public/app/angular/diff-view.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import angular from 'angular';
|
||||
import coreModule from '../core/core_module';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
|
||||
export class DeltaCtrl {
|
||||
observer: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor() {
|
||||
const waitForCompile = () => {};
|
||||
|
||||
this.observer = new MutationObserver(waitForCompile);
|
||||
|
||||
const observerConfig = {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
characterData: false,
|
||||
childList: true,
|
||||
subtree: false,
|
||||
};
|
||||
|
||||
this.observer.observe(angular.element('.delta-html')[0], observerConfig);
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export function delta() {
|
||||
return {
|
||||
controller: DeltaCtrl,
|
||||
replace: false,
|
||||
restrict: 'A',
|
||||
};
|
||||
}
|
||||
coreModule.directive('diffDelta', delta);
|
||||
|
||||
// Link to JSON line number
|
||||
export class LinkJSONCtrl {
|
||||
/** @ngInject */
|
||||
constructor(private $scope: any, private $rootScope: GrafanaRootScope, private $anchorScroll: any) {}
|
||||
|
||||
goToLine(line: number) {
|
||||
let unbind: () => void;
|
||||
|
||||
const scroll = () => {
|
||||
this.$anchorScroll(`l${line}`);
|
||||
unbind();
|
||||
};
|
||||
|
||||
this.$scope.switchView().then(() => {
|
||||
unbind = this.$rootScope.$on('json-diff-ready', scroll.bind(this));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function linkJson() {
|
||||
return {
|
||||
controller: LinkJSONCtrl,
|
||||
controllerAs: 'ctrl',
|
||||
replace: true,
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
line: '@lineDisplay',
|
||||
link: '@lineLink',
|
||||
switchView: '&',
|
||||
},
|
||||
template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>`,
|
||||
};
|
||||
}
|
||||
coreModule.directive('diffLinkJson', linkJson);
|
||||
274
public/app/angular/dropdown_typeahead.ts
Normal file
274
public/app/angular/dropdown_typeahead.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
import { each, reduce } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../core/core_module';
|
||||
|
||||
/** @ngInject */
|
||||
export function dropdownTypeahead($compile: any) {
|
||||
const inputTemplate =
|
||||
'<input type="text"' +
|
||||
' class="gf-form-input input-medium tight-form-input"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
const buttonTemplate =
|
||||
'<a class="gf-form-label tight-form-func dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
|
||||
' ><i class="fa fa-plus"></i></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
menuItems: '=dropdownTypeahead',
|
||||
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
|
||||
model: '=ngModel',
|
||||
},
|
||||
link: ($scope: any, elem: any, attrs: any) => {
|
||||
const $input = $(inputTemplate);
|
||||
const $button = $(buttonTemplate);
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
if (attrs.linkText) {
|
||||
$button.html(attrs.linkText);
|
||||
}
|
||||
|
||||
if (attrs.ngModel) {
|
||||
$scope.$watch('model', (newValue: any) => {
|
||||
each($scope.menuItems, (item) => {
|
||||
each(item.submenu, (subItem) => {
|
||||
if (subItem.value === newValue) {
|
||||
$button.html(subItem.text);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const typeaheadValues = reduce(
|
||||
$scope.menuItems,
|
||||
(memo: any[], value, index) => {
|
||||
if (!value.submenu) {
|
||||
value.click = 'menuItemSelected(' + index + ')';
|
||||
memo.push(value.text);
|
||||
} else {
|
||||
each(value.submenu, (item, subIndex) => {
|
||||
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
|
||||
memo.push(value.text + ' ' + item.text);
|
||||
});
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const closeDropdownMenu = () => {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
elem.removeClass('open');
|
||||
};
|
||||
|
||||
$scope.menuItemSelected = (index: number, subIndex: number) => {
|
||||
const menuItem = $scope.menuItems[index];
|
||||
const payload: any = { $item: menuItem };
|
||||
if (menuItem.submenu && subIndex !== void 0) {
|
||||
payload.$subItem = menuItem.submenu[subIndex];
|
||||
}
|
||||
$scope.dropdownTypeaheadOnSelect(payload);
|
||||
closeDropdownMenu();
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: typeaheadValues,
|
||||
minLength: 1,
|
||||
items: 10,
|
||||
updater: (value: string) => {
|
||||
const result: any = {};
|
||||
each($scope.menuItems, (menuItem) => {
|
||||
each(menuItem.submenu, (submenuItem) => {
|
||||
if (value === menuItem.text + ' ' + submenuItem.text) {
|
||||
result.$subItem = submenuItem;
|
||||
result.$item = menuItem;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (result.$item) {
|
||||
$scope.$apply(() => {
|
||||
$scope.dropdownTypeaheadOnSelect(result);
|
||||
});
|
||||
}
|
||||
|
||||
$input.trigger('blur');
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
$button.click(() => {
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.keyup(() => {
|
||||
elem.toggleClass('open', $input.val() === '');
|
||||
});
|
||||
|
||||
elem.mousedown((evt: Event) => {
|
||||
evt.preventDefault();
|
||||
});
|
||||
|
||||
$input.blur(() => {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
// clicking the function dropdown menu won't
|
||||
// work if you remove class at once
|
||||
setTimeout(() => {
|
||||
elem.removeClass('open');
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function dropdownTypeahead2($compile: any) {
|
||||
const inputTemplate =
|
||||
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
const buttonTemplate =
|
||||
'<a class="{{buttonTemplateClass}} dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
|
||||
' ><i class="fa fa-plus"></i></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
menuItems: '=dropdownTypeahead2',
|
||||
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
|
||||
model: '=ngModel',
|
||||
buttonTemplateClass: '@',
|
||||
},
|
||||
link: ($scope: any, elem: any, attrs: any) => {
|
||||
const $input = $(inputTemplate);
|
||||
|
||||
if (!$scope.buttonTemplateClass) {
|
||||
$scope.buttonTemplateClass = 'gf-form-input';
|
||||
}
|
||||
|
||||
const $button = $(buttonTemplate);
|
||||
const timeoutId = {
|
||||
blur: null as any,
|
||||
};
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
if (attrs.linkText) {
|
||||
$button.html(attrs.linkText);
|
||||
}
|
||||
|
||||
if (attrs.ngModel) {
|
||||
$scope.$watch('model', (newValue: any) => {
|
||||
each($scope.menuItems, (item) => {
|
||||
each(item.submenu, (subItem) => {
|
||||
if (subItem.value === newValue) {
|
||||
$button.html(subItem.text);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const typeaheadValues = reduce(
|
||||
$scope.menuItems,
|
||||
(memo: any[], value, index) => {
|
||||
if (!value.submenu) {
|
||||
value.click = 'menuItemSelected(' + index + ')';
|
||||
memo.push(value.text);
|
||||
} else {
|
||||
each(value.submenu, (item, subIndex) => {
|
||||
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
|
||||
memo.push(value.text + ' ' + item.text);
|
||||
});
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const closeDropdownMenu = () => {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
elem.removeClass('open');
|
||||
};
|
||||
|
||||
$scope.menuItemSelected = (index: number, subIndex: number) => {
|
||||
const menuItem = $scope.menuItems[index];
|
||||
const payload: any = { $item: menuItem };
|
||||
if (menuItem.submenu && subIndex !== void 0) {
|
||||
payload.$subItem = menuItem.submenu[subIndex];
|
||||
}
|
||||
$scope.dropdownTypeaheadOnSelect(payload);
|
||||
closeDropdownMenu();
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: typeaheadValues,
|
||||
minLength: 1,
|
||||
items: 10,
|
||||
updater: (value: string) => {
|
||||
const result: any = {};
|
||||
each($scope.menuItems, (menuItem) => {
|
||||
each(menuItem.submenu, (submenuItem) => {
|
||||
if (value === menuItem.text + ' ' + submenuItem.text) {
|
||||
result.$subItem = submenuItem;
|
||||
result.$item = menuItem;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (result.$item) {
|
||||
$scope.$apply(() => {
|
||||
$scope.dropdownTypeaheadOnSelect(result);
|
||||
});
|
||||
}
|
||||
|
||||
$input.trigger('blur');
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
$button.click(() => {
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.keyup(() => {
|
||||
elem.toggleClass('open', $input.val() === '');
|
||||
});
|
||||
|
||||
elem.mousedown((evt: Event) => {
|
||||
evt.preventDefault();
|
||||
timeoutId.blur = null;
|
||||
});
|
||||
|
||||
$input.blur(() => {
|
||||
timeoutId.blur = setTimeout(() => {
|
||||
closeDropdownMenu();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('dropdownTypeahead', dropdownTypeahead);
|
||||
coreModule.directive('dropdownTypeahead2', dropdownTypeahead2);
|
||||
29
public/app/angular/give_focus.ts
Normal file
29
public/app/angular/give_focus.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import coreModule from '../core/core_module';
|
||||
|
||||
coreModule.directive('giveFocus', () => {
|
||||
return (scope: any, element: any, attrs: any) => {
|
||||
element.click((e: any) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
scope.$watch(
|
||||
attrs.giveFocus,
|
||||
(newValue: any) => {
|
||||
if (!newValue) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
element.focus();
|
||||
const domEl: any = element[0];
|
||||
if (domEl.setSelectionRange) {
|
||||
const pos = element.val().length * 2;
|
||||
domEl.setSelectionRange(pos, pos);
|
||||
}
|
||||
}, 200);
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default {};
|
||||
266
public/app/angular/metric_segment.ts
Normal file
266
public/app/angular/metric_segment.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { debounce, find, indexOf, map, escape, unescape } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../core/core_module';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
/** @ngInject */
|
||||
export function metricSegment($compile: any, $sce: any, templateSrv: TemplateSrv) {
|
||||
const inputTemplate =
|
||||
'<input type="text" data-provide="typeahead" ' +
|
||||
' class="gf-form-input input-medium"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
const linkTemplate =
|
||||
'<a class="gf-form-label" ng-class="segment.cssClass" ' +
|
||||
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
|
||||
const selectTemplate =
|
||||
'<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
|
||||
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
segment: '=',
|
||||
getOptions: '&',
|
||||
onChange: '&',
|
||||
debounce: '@',
|
||||
},
|
||||
link: ($scope: any, elem: any) => {
|
||||
const $input = $(inputTemplate);
|
||||
const segment = $scope.segment;
|
||||
const $button = $(segment.selectMode ? selectTemplate : linkTemplate);
|
||||
let options = null;
|
||||
let cancelBlur: any = null;
|
||||
let linkMode = true;
|
||||
const debounceLookup = $scope.debounce;
|
||||
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
$scope.updateVariableValue = (value: string) => {
|
||||
if (value === '' || segment.value === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.$apply(() => {
|
||||
const selected: any = find($scope.altSegments, { value: value });
|
||||
if (selected) {
|
||||
segment.value = selected.value;
|
||||
segment.html = selected.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(selected.value));
|
||||
segment.fake = false;
|
||||
segment.expandable = selected.expandable;
|
||||
|
||||
if (selected.type) {
|
||||
segment.type = selected.type;
|
||||
}
|
||||
} else if (segment.custom !== 'false') {
|
||||
segment.value = value;
|
||||
segment.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(value));
|
||||
segment.expandable = true;
|
||||
segment.fake = false;
|
||||
}
|
||||
|
||||
$scope.onChange();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.switchToLink = (fromClick: boolean) => {
|
||||
if (linkMode && !fromClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(cancelBlur);
|
||||
cancelBlur = null;
|
||||
linkMode = true;
|
||||
$input.hide();
|
||||
$button.show();
|
||||
$scope.updateVariableValue($input.val());
|
||||
};
|
||||
|
||||
$scope.inputBlur = () => {
|
||||
// happens long before the click event on the typeahead options
|
||||
// need to have long delay because the blur
|
||||
cancelBlur = setTimeout($scope.switchToLink, 200);
|
||||
};
|
||||
|
||||
$scope.source = (query: string, callback: any) => {
|
||||
$scope.$apply(() => {
|
||||
$scope.getOptions({ $query: query }).then((altSegments: any) => {
|
||||
$scope.altSegments = altSegments;
|
||||
options = map($scope.altSegments, (alt) => {
|
||||
return escape(alt.value);
|
||||
});
|
||||
|
||||
// add custom values
|
||||
if (segment.custom !== 'false') {
|
||||
if (!segment.fake && indexOf(options, segment.value) === -1) {
|
||||
options.unshift(escape(segment.value));
|
||||
}
|
||||
}
|
||||
|
||||
callback(options);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updater = (value: string) => {
|
||||
value = unescape(value);
|
||||
if (value === segment.value) {
|
||||
clearTimeout(cancelBlur);
|
||||
$input.focus();
|
||||
return value;
|
||||
}
|
||||
|
||||
$input.val(value);
|
||||
$scope.switchToLink(true);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
$scope.matcher = function (item: string) {
|
||||
if (linkMode) {
|
||||
return false;
|
||||
}
|
||||
let str = this.query;
|
||||
if (str[0] === '/') {
|
||||
str = str.substring(1);
|
||||
}
|
||||
if (str[str.length - 1] === '/') {
|
||||
str = str.substring(0, str.length - 1);
|
||||
}
|
||||
try {
|
||||
return item.toLowerCase().match(str.toLowerCase());
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: $scope.source,
|
||||
minLength: 0,
|
||||
items: 10000,
|
||||
updater: $scope.updater,
|
||||
matcher: $scope.matcher,
|
||||
});
|
||||
|
||||
const typeahead = $input.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
this.query = this.$element.val() || '';
|
||||
const items = this.source(this.query, $.proxy(this.process, this));
|
||||
return items ? this.process(items) : items;
|
||||
};
|
||||
|
||||
if (debounceLookup) {
|
||||
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
|
||||
}
|
||||
|
||||
$button.keydown((evt) => {
|
||||
// trigger typeahead on down arrow or enter key
|
||||
if (evt.keyCode === 40 || evt.keyCode === 13) {
|
||||
$button.click();
|
||||
}
|
||||
});
|
||||
|
||||
$button.click(() => {
|
||||
options = null;
|
||||
$input.css('width', Math.max($button.width()!, 80) + 16 + 'px');
|
||||
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
|
||||
linkMode = false;
|
||||
|
||||
const typeahead = $input.data('typeahead');
|
||||
if (typeahead) {
|
||||
$input.val('');
|
||||
typeahead.lookup();
|
||||
}
|
||||
});
|
||||
|
||||
$input.blur($scope.inputBlur);
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function metricSegmentModel(uiSegmentSrv: any) {
|
||||
return {
|
||||
template:
|
||||
'<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
property: '=',
|
||||
options: '=',
|
||||
getOptions: '&',
|
||||
onChange: '&',
|
||||
},
|
||||
link: {
|
||||
pre: function postLink($scope: any, elem: any, attrs: any) {
|
||||
let cachedOptions: any;
|
||||
|
||||
$scope.valueToSegment = (value: any) => {
|
||||
const option: any = find($scope.options, { value: value });
|
||||
const segment = {
|
||||
cssClass: attrs.cssClass,
|
||||
custom: attrs.custom,
|
||||
value: option ? option.text : value,
|
||||
selectMode: attrs.selectMode,
|
||||
};
|
||||
|
||||
return uiSegmentSrv.newSegment(segment);
|
||||
};
|
||||
|
||||
$scope.getOptionsInternal = () => {
|
||||
if ($scope.options) {
|
||||
cachedOptions = $scope.options;
|
||||
return Promise.resolve(
|
||||
map($scope.options, (option) => {
|
||||
return { value: option.text };
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return $scope.getOptions().then((options: any) => {
|
||||
cachedOptions = options;
|
||||
return map(options, (option) => {
|
||||
if (option.html) {
|
||||
return option;
|
||||
}
|
||||
return { value: option.text };
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onSegmentChange = () => {
|
||||
if (cachedOptions) {
|
||||
const option: any = find(cachedOptions, { text: $scope.segment.value });
|
||||
if (option && option.value !== $scope.property) {
|
||||
$scope.property = option.value;
|
||||
} else if (attrs.custom !== 'false') {
|
||||
$scope.property = $scope.segment.value;
|
||||
}
|
||||
} else {
|
||||
$scope.property = $scope.segment.value;
|
||||
}
|
||||
|
||||
// needs to call this after digest so
|
||||
// property is synced with outerscope
|
||||
$scope.$$postDigest(() => {
|
||||
$scope.$apply(() => {
|
||||
$scope.onChange();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.segment = $scope.valueToSegment($scope.property);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('metricSegment', metricSegment);
|
||||
coreModule.directive('metricSegmentModel', metricSegmentModel);
|
||||
216
public/app/angular/misc.ts
Normal file
216
public/app/angular/misc.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import angular from 'angular';
|
||||
import Clipboard from 'clipboard';
|
||||
import coreModule from '../core/core_module';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
/** @ngInject */
|
||||
function tip($compile: any) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: (scope: any, elem: any, attrs: any) => {
|
||||
let _t =
|
||||
'<i class="grafana-tip fa fa-' +
|
||||
(attrs.icon || 'question-circle') +
|
||||
'" bs-tooltip="\'' +
|
||||
kbn.addSlashes(elem.text()) +
|
||||
'\'"></i>';
|
||||
_t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
|
||||
elem.replaceWith($compile(angular.element(_t))(scope));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function clipboardButton() {
|
||||
return {
|
||||
scope: {
|
||||
getText: '&clipboardButton',
|
||||
},
|
||||
link: (scope: any, elem: any) => {
|
||||
scope.clipboard = new Clipboard(elem[0], {
|
||||
text: () => {
|
||||
return scope.getText();
|
||||
},
|
||||
});
|
||||
|
||||
scope.clipboard.on('success', () => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
if (scope.clipboard) {
|
||||
scope.clipboard.destroy();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function compile($compile: any) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: (scope: any, element: any, attrs: any) => {
|
||||
scope.$watch(
|
||||
(scope: any) => {
|
||||
return scope.$eval(attrs.compile);
|
||||
},
|
||||
(value: any) => {
|
||||
element.html(value);
|
||||
$compile(element.contents())(scope);
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function watchChange() {
|
||||
return {
|
||||
scope: { onchange: '&watchChange' },
|
||||
link: (scope: any, element: any) => {
|
||||
element.on('input', () => {
|
||||
scope.$apply(() => {
|
||||
scope.onchange({ inputValue: element.val() });
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function editorOptBool($compile: any) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: (scope: any, elem: any, attrs: any) => {
|
||||
const ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
|
||||
const tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
|
||||
const showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : '';
|
||||
|
||||
const template =
|
||||
'<div class="editor-option gf-form-checkbox text-center"' +
|
||||
showIf +
|
||||
'>' +
|
||||
' <label for="' +
|
||||
attrs.model +
|
||||
'" class="small">' +
|
||||
attrs.text +
|
||||
tip +
|
||||
'</label>' +
|
||||
'<input class="cr1" id="' +
|
||||
attrs.model +
|
||||
'" type="checkbox" ' +
|
||||
' ng-model="' +
|
||||
attrs.model +
|
||||
'"' +
|
||||
ngchange +
|
||||
' ng-checked="' +
|
||||
attrs.model +
|
||||
'"></input>' +
|
||||
' <label for="' +
|
||||
attrs.model +
|
||||
'" class="cr1"></label>';
|
||||
elem.replaceWith($compile(angular.element(template))(scope));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function editorCheckbox($compile: any, $interpolate: any) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: (scope: any, elem: any, attrs: any) => {
|
||||
const text = $interpolate(attrs.text)(scope);
|
||||
const model = $interpolate(attrs.model)(scope);
|
||||
const ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
|
||||
const tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
|
||||
const label = '<label for="' + scope.$id + model + '" class="checkbox-label">' + text + tip + '</label>';
|
||||
|
||||
let template =
|
||||
'<input class="cr1" id="' +
|
||||
scope.$id +
|
||||
model +
|
||||
'" type="checkbox" ' +
|
||||
' ng-model="' +
|
||||
model +
|
||||
'"' +
|
||||
ngchange +
|
||||
' ng-checked="' +
|
||||
model +
|
||||
'"></input>' +
|
||||
' <label for="' +
|
||||
scope.$id +
|
||||
model +
|
||||
'" class="cr1"></label>';
|
||||
|
||||
template = template + label;
|
||||
elem.addClass('gf-form-checkbox');
|
||||
elem.html($compile(angular.element(template))(scope));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function gfDropdown($parse: any, $compile: any, $timeout: any) {
|
||||
function buildTemplate(items: any, placement?: any) {
|
||||
const upclass = placement === 'top' ? 'dropup' : '';
|
||||
const ul = ['<ul class="dropdown-menu ' + upclass + '" role="menu" aria-labelledby="drop1">', '</ul>'];
|
||||
|
||||
for (let index = 0; index < items.length; index++) {
|
||||
const item = items[index];
|
||||
|
||||
if (item.divider) {
|
||||
ul.splice(index + 1, 0, '<li class="divider"></li>');
|
||||
continue;
|
||||
}
|
||||
|
||||
let li =
|
||||
'<li' +
|
||||
(item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') +
|
||||
'>' +
|
||||
'<a tabindex="-1" ng-href="' +
|
||||
(item.href || '') +
|
||||
'"' +
|
||||
(item.click ? ' ng-click="' + item.click + '"' : '') +
|
||||
(item.target ? ' target="' + item.target + '"' : '') +
|
||||
(item.method ? ' data-method="' + item.method + '"' : '') +
|
||||
'>' +
|
||||
(item.text || '') +
|
||||
'</a>';
|
||||
|
||||
if (item.submenu && item.submenu.length) {
|
||||
li += buildTemplate(item.submenu).join('\n');
|
||||
}
|
||||
|
||||
li += '</li>';
|
||||
ul.splice(index + 1, 0, li);
|
||||
}
|
||||
|
||||
return ul;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
scope: true,
|
||||
link: function postLink(scope: any, iElement: any, iAttrs: any) {
|
||||
const getter = $parse(iAttrs.gfDropdown),
|
||||
items = getter(scope);
|
||||
$timeout(() => {
|
||||
const placement = iElement.data('placement');
|
||||
const dropdown = angular.element(buildTemplate(items, placement).join(''));
|
||||
dropdown.insertAfter(iElement);
|
||||
$compile(iElement.next('ul.dropdown-menu'))(scope);
|
||||
});
|
||||
|
||||
iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('tip', tip);
|
||||
coreModule.directive('clipboardButton', clipboardButton);
|
||||
coreModule.directive('compile', compile);
|
||||
coreModule.directive('watchChange', watchChange);
|
||||
coreModule.directive('editorOptBool', editorOptBool);
|
||||
coreModule.directive('editorCheckbox', editorCheckbox);
|
||||
coreModule.directive('gfDropdown', gfDropdown);
|
||||
59
public/app/angular/ng_model_on_blur.ts
Normal file
59
public/app/angular/ng_model_on_blur.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import coreModule from '../core/core_module';
|
||||
import { rangeUtil } from '@grafana/data';
|
||||
|
||||
function ngModelOnBlur() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 1,
|
||||
require: 'ngModel',
|
||||
link: (scope: any, elm: any, attr: any, ngModelCtrl: any) => {
|
||||
if (attr.type === 'radio' || attr.type === 'checkbox') {
|
||||
return;
|
||||
}
|
||||
|
||||
elm.off('input keydown change');
|
||||
elm.bind('blur', () => {
|
||||
scope.$apply(() => {
|
||||
ngModelCtrl.$setViewValue(elm.val());
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function emptyToNull() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: (scope: any, elm: any, attrs: any, ctrl: any) => {
|
||||
ctrl.$parsers.push((viewValue: any) => {
|
||||
if (viewValue === '') {
|
||||
return null;
|
||||
}
|
||||
return viewValue;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validTimeSpan() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: (scope: any, elm: any, attrs: any, ctrl: any) => {
|
||||
ctrl.$validators.integer = (modelValue: any, viewValue: any) => {
|
||||
if (ctrl.$isEmpty(modelValue)) {
|
||||
return true;
|
||||
}
|
||||
if (viewValue.indexOf('$') === 0 || viewValue.indexOf('+$') === 0) {
|
||||
return true; // allow template variable
|
||||
}
|
||||
const info = rangeUtil.describeTextRange(viewValue);
|
||||
return info.invalid !== true;
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('ngModelOnblur', ngModelOnBlur);
|
||||
coreModule.directive('emptyToNull', emptyToNull);
|
||||
coreModule.directive('validTimeSpan', validTimeSpan);
|
||||
73
public/app/angular/rebuild_on_change.ts
Normal file
73
public/app/angular/rebuild_on_change.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../core/core_module';
|
||||
|
||||
function getBlockNodes(nodes: any[]) {
|
||||
let node = nodes[0];
|
||||
const endNode = nodes[nodes.length - 1];
|
||||
let blockNodes: any[] | undefined;
|
||||
node = node.nextSibling;
|
||||
|
||||
for (let i = 1; node !== endNode && node; i++) {
|
||||
if (blockNodes || nodes[i] !== node) {
|
||||
if (!blockNodes) {
|
||||
blockNodes = $([].slice.call(nodes, 0, i)) as any;
|
||||
}
|
||||
|
||||
blockNodes!.push(node);
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return blockNodes || nodes;
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function rebuildOnChange($animate: any) {
|
||||
return {
|
||||
multiElement: true,
|
||||
terminal: true,
|
||||
transclude: true,
|
||||
priority: 600,
|
||||
restrict: 'E',
|
||||
link: (scope: any, elem: any, attrs: any, ctrl: any, transclude: any) => {
|
||||
let block: any, childScope: any, previousElements: any;
|
||||
|
||||
function cleanUp() {
|
||||
if (previousElements) {
|
||||
previousElements.remove();
|
||||
previousElements = null;
|
||||
}
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
childScope = null;
|
||||
}
|
||||
if (block) {
|
||||
previousElements = getBlockNodes(block.clone);
|
||||
$animate.leave(previousElements).then(() => {
|
||||
previousElements = null;
|
||||
});
|
||||
block = null;
|
||||
}
|
||||
}
|
||||
|
||||
scope.$watch(attrs.property, function rebuildOnChangeAction(value: any, oldValue: any) {
|
||||
if (childScope && value !== oldValue) {
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
if (!childScope && (value || attrs.showNull)) {
|
||||
transclude((clone: any, newScope: any) => {
|
||||
childScope = newScope;
|
||||
clone[clone.length++] = document.createComment(' end rebuild on change ');
|
||||
block = { clone: clone };
|
||||
$animate.enter(clone, elem.parent(), elem);
|
||||
});
|
||||
} else {
|
||||
cleanUp();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('rebuildOnChange', rebuildOnChange);
|
||||
9
public/app/angular/services.ts
Normal file
9
public/app/angular/services.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import './alert_srv';
|
||||
import './util_srv';
|
||||
import './context_srv';
|
||||
import './timer';
|
||||
import './analytics';
|
||||
import './popover_srv';
|
||||
import './segment_srv';
|
||||
import './backend_srv';
|
||||
import './dynamic_directive_srv';
|
||||
118
public/app/angular/tags.ts
Normal file
118
public/app/angular/tags.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import angular from 'angular';
|
||||
import { getTagColorsFromName } from '@grafana/ui';
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../core/core_module';
|
||||
import 'vendor/tagsinput/bootstrap-tagsinput.js';
|
||||
|
||||
function setColor(name: string, element: JQuery) {
|
||||
const { color, borderColor } = getTagColorsFromName(name);
|
||||
element.css('background-color', color);
|
||||
element.css('border-color', borderColor);
|
||||
}
|
||||
|
||||
function tagColorFromName() {
|
||||
return {
|
||||
scope: { tagColorFromName: '=' },
|
||||
link: (scope: any, element: any) => {
|
||||
setColor(scope.tagColorFromName, element);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function bootstrapTagsinput() {
|
||||
function getItemProperty(scope: any, property: any) {
|
||||
if (!property) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (angular.isFunction(scope.$parent[property])) {
|
||||
return scope.$parent[property];
|
||||
}
|
||||
|
||||
return (item: any) => {
|
||||
return item[property];
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
scope: {
|
||||
model: '=ngModel',
|
||||
onTagsUpdated: '&',
|
||||
},
|
||||
template: '<select multiple></select>',
|
||||
replace: false,
|
||||
link: function (scope: any, element: any, attrs: any) {
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
const select = $('select', element);
|
||||
|
||||
if (attrs.placeholder) {
|
||||
select.attr('placeholder', attrs.placeholder);
|
||||
}
|
||||
|
||||
select.tagsinput({
|
||||
typeahead: {
|
||||
source: angular.isFunction(scope.$parent[attrs.typeaheadSource])
|
||||
? scope.$parent[attrs.typeaheadSource]
|
||||
: null,
|
||||
},
|
||||
widthClass: attrs.widthClass,
|
||||
itemValue: getItemProperty(scope, attrs.itemvalue),
|
||||
itemText: getItemProperty(scope, attrs.itemtext),
|
||||
tagClass: angular.isFunction(scope.$parent[attrs.tagclass])
|
||||
? scope.$parent[attrs.tagclass]
|
||||
: () => {
|
||||
return attrs.tagclass;
|
||||
},
|
||||
});
|
||||
|
||||
select.on('itemAdded', (event: any) => {
|
||||
if (scope.model.indexOf(event.item) === -1) {
|
||||
scope.model.push(event.item);
|
||||
if (scope.onTagsUpdated) {
|
||||
scope.onTagsUpdated();
|
||||
}
|
||||
}
|
||||
const tagElement = select
|
||||
.next()
|
||||
.children('span')
|
||||
.filter(() => {
|
||||
return $(this).text() === event.item;
|
||||
});
|
||||
setColor(event.item, tagElement);
|
||||
});
|
||||
|
||||
select.on('itemRemoved', (event: any) => {
|
||||
const idx = scope.model.indexOf(event.item);
|
||||
if (idx !== -1) {
|
||||
scope.model.splice(idx, 1);
|
||||
if (scope.onTagsUpdated) {
|
||||
scope.onTagsUpdated();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch(
|
||||
'model',
|
||||
() => {
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
select.tagsinput('removeAll');
|
||||
|
||||
for (let i = 0; i < scope.model.length; i++) {
|
||||
select.tagsinput('add', scope.model[i]);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('tagColorFromName', tagColorFromName);
|
||||
coreModule.directive('bootstrapTagsinput', bootstrapTagsinput);
|
||||
Reference in New Issue
Block a user