Browse Source

Conditionally show widget header actions

pull/5295/head
Igor Kulikov 5 years ago
parent
commit
23121c5b5a
  1. 11
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
  2. 31
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  3. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
  4. 33
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  5. 36
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  6. 5
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  7. 57
      ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
  8. 2
      ui-ngx/src/app/shared/models/widget.models.ts
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

11
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html

@ -54,6 +54,17 @@
<tb-material-icon-select
formControlName="icon">
</tb-material-icon-select>
<mat-checkbox *ngIf="displayShowWidgetActionForm()" formControlName="useShowWidgetActionFunction" style="padding-bottom: 16px;">
{{ 'widget-config.use-show-widget-action-function' | translate }}
</mat-checkbox>
<tb-js-func *ngIf="displayShowWidgetActionForm() && widgetActionFormGroup.get('useShowWidgetActionFunction').value"
formControlName="showWidgetActionFunction"
[functionArgs]="['widgetContext', 'data']"
[globalVariables]="functionScopeVariables"
[validationArgs]="[]"
[resultType]="'boolean'"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
<mat-form-field class="mat-block">
<mat-label translate>widget-config.action-type</mat-label>
<mat-select required formControlName="type">

31
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts

@ -38,7 +38,12 @@ import {
WidgetActionsData
} from '@home/components/widget/action/manage-widget-actions.component.models';
import { UtilsService } from '@core/services/utils.service';
import { WidgetActionSource, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models';
import {
WidgetActionSource,
widgetActionSources,
WidgetActionType,
widgetActionTypeTranslationMap
} from '@shared/models/widget.models';
import { map, mergeMap, startWith, tap } from 'rxjs/operators';
import { DashboardService } from '@core/http/dashboard.service';
import { Dashboard } from '@shared/models/dashboard.models';
@ -124,17 +129,41 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.fb.control(this.action.name, [this.validateActionName(), Validators.required]));
this.widgetActionFormGroup.addControl('icon',
this.fb.control(this.action.icon, [Validators.required]));
this.widgetActionFormGroup.addControl('useShowWidgetActionFunction',
this.fb.control(this.action.useShowWidgetActionFunction, []));
this.widgetActionFormGroup.addControl('showWidgetActionFunction',
this.fb.control(this.action.showWidgetActionFunction || 'return true;', []));
this.widgetActionFormGroup.addControl('type',
this.fb.control(this.action.type, [Validators.required]));
this.updateShowWidgetActionForm();
this.updateActionTypeFormGroup(this.action.type, this.action);
this.widgetActionFormGroup.get('type').valueChanges.subscribe((type: WidgetActionType) => {
this.updateActionTypeFormGroup(type);
});
this.widgetActionFormGroup.get('actionSourceId').valueChanges.subscribe(() => {
this.widgetActionFormGroup.get('name').updateValueAndValidity();
this.updateShowWidgetActionForm();
});
this.widgetActionFormGroup.get('useShowWidgetActionFunction').valueChanges.subscribe(() => {
this.updateShowWidgetActionForm();
});
}
displayShowWidgetActionForm(): boolean {
return this.widgetActionFormGroup.get('actionSourceId').value === widgetActionSources.headerButton.value;
}
private updateShowWidgetActionForm() {
const actionSourceId = this.widgetActionFormGroup.get('actionSourceId').value;
const useShowWidgetActionFunction = this.widgetActionFormGroup.get('useShowWidgetActionFunction').value;
if (actionSourceId === widgetActionSources.headerButton.value && useShowWidgetActionFunction) {
this.widgetActionFormGroup.get('showWidgetActionFunction').setValidators([Validators.required]);
} else {
this.widgetActionFormGroup.get('showWidgetActionFunction').clearValidators();
}
this.widgetActionFormGroup.get('showWidgetActionFunction').updateValueAndValidity();
}
private updateActionTypeFormGroup(type?: WidgetActionType, action?: WidgetActionDescriptorInfo) {
this.actionTypeFormGroup = this.fb.group({});
if (type) {

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts

@ -330,7 +330,7 @@ export function parseData(input: DatasourceData[]): FormattedData[] {
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el.data.length ? el.data.length - 1 : 0;
const indexDate = el.data.length - 1;
if (!obj.hasOwnProperty(el.dataKey.label) || el.data[indexDate][1] !== '') {
obj[el.dataKey.label] = el.data[indexDate][1];
obj[el.dataKey.label + '|ts'] = el.data[indexDate][0];

33
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -55,9 +55,17 @@ import { AppState } from '@core/core.state';
import { WidgetService } from '@core/http/widget.service';
import { UtilsService } from '@core/services/utils.service';
import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs';
import { deepClone, insertVariable, isDefined, objToBase64, objToBase64URI, validateEntityId } from '@core/utils';
import {
IDynamicWidgetComponent,
deepClone,
insertVariable,
isDefined,
isNotEmptyStr,
objToBase64,
objToBase64URI,
validateEntityId
} from '@core/utils';
import {
IDynamicWidgetComponent, ShowWidgetHeaderActionFunction,
WidgetContext,
WidgetHeaderAction,
WidgetInfo,
@ -290,12 +298,25 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetContext.customHeaderActions = [];
const headerActionsDescriptors = this.getActionDescriptors(widgetActionSources.headerButton.value);
headerActionsDescriptors.forEach((descriptor) => {
headerActionsDescriptors.forEach((descriptor) =>
{
let useShowWidgetHeaderActionFunction = descriptor.useShowWidgetActionFunction || false;
let showWidgetHeaderActionFunction: ShowWidgetHeaderActionFunction = null;
if (useShowWidgetHeaderActionFunction && isNotEmptyStr(descriptor.showWidgetActionFunction)) {
try {
showWidgetHeaderActionFunction =
new Function('widgetContext', 'data', descriptor.showWidgetActionFunction) as ShowWidgetHeaderActionFunction;
} catch (e) {
useShowWidgetHeaderActionFunction = false;
}
}
const headerAction: WidgetHeaderAction = {
name: descriptor.name,
displayName: descriptor.displayName,
icon: descriptor.icon,
descriptor,
useShowWidgetHeaderActionFunction,
showWidgetHeaderActionFunction,
onAction: $event => {
const entityInfo = this.getActiveEntityInfo();
const entityId = entityInfo ? entityInfo.entityId : null;
@ -507,6 +528,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetInstanceInited = true;
if (this.dataUpdatePending) {
this.widgetTypeInstance.onDataUpdated();
setTimeout(() => {
this.dashboardWidget.updateCustomHeaderActions(true);
}, 0);
this.dataUpdatePending = false;
}
if (this.pendingMessage) {
@ -844,6 +868,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
if (this.displayWidgetInstance()) {
if (this.widgetInstanceInited) {
this.widgetTypeInstance.onDataUpdated();
setTimeout(() => {
this.dashboardWidget.updateCustomHeaderActions(true);
}, 0);
} else {
this.dataUpdatePending = true;
}

36
ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

@ -25,6 +25,8 @@ import { IterableDiffer, KeyValueDiffer } from '@angular/core';
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
import { enumerable } from '@shared/decorators/enumerable';
import { UtilsService } from '@core/services/utils.service';
import { FormattedData } from '@home/components/widget/lib/maps/map-models';
import { parseData } from '@home/components/widget/lib/maps/common-maps-utils';
export interface WidgetsData {
widgets: Array<Widget>;
@ -438,13 +440,45 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.showWidgetActions = !this.widgetContext.hideTitlePanel;
this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : [];
this.updateCustomHeaderActions();
this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : [];
if (detectChanges) {
this.widgetContext.detectContainerChanges();
}
}
updateCustomHeaderActions(detectChanges = false) {
let customHeaderActions: Array<WidgetHeaderAction>;
if (this.widgetContext.customHeaderActions) {
let data: FormattedData[] = [];
if (this.widgetContext.customHeaderActions.some(action => action.useShowWidgetHeaderActionFunction)) {
data = parseData(this.widgetContext.data || []);
}
customHeaderActions = this.widgetContext.customHeaderActions.filter(action => this.filterCustomHeaderAction(action, data));
} else {
customHeaderActions = [];
}
if (!isEqual(this.customHeaderActions, customHeaderActions)) {
this.customHeaderActions = customHeaderActions;
if (detectChanges) {
this.widgetContext.detectContainerChanges();
}
}
}
private filterCustomHeaderAction(action: WidgetHeaderAction, data: FormattedData[]): boolean {
if (action.useShowWidgetHeaderActionFunction) {
try {
return action.showWidgetHeaderActionFunction(this.widgetContext, data);
} catch (e) {
console.warn('Failed to execute showWidgetHeaderActionFunction', e);
return false;
}
} else {
return true;
}
}
@enumerable(true)
get x(): number {
let res;

5
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -79,6 +79,7 @@ import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { FormattedData } from '@home/components/widget/lib/maps/map-models';
export interface IWidgetAction {
name: string;
@ -86,9 +87,13 @@ export interface IWidgetAction {
onAction: ($event: Event) => void;
}
export type ShowWidgetHeaderActionFunction = (ctx: WidgetContext, data: FormattedData[]) => boolean;
export interface WidgetHeaderAction extends IWidgetAction {
displayName: string;
descriptor: WidgetActionDescriptor;
useShowWidgetHeaderActionFunction: boolean;
showWidgetHeaderActionFunction: ShowWidgetHeaderActionFunction;
}
export interface WidgetAction extends IWidgetAction {

57
ui-ngx/src/app/shared/models/ace/widget-completion.models.ts

@ -390,6 +390,63 @@ export const widgetContextCompletions: TbEditorCompletions = {
meta: 'property',
type: 'number'
},
currentUser: {
description: 'Current user object.',
meta: 'property',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/user.model.ts#L45">AuthUser</a>',
children: {
sub: {
description: 'User subject (email).',
meta: 'property',
type: 'string'
},
scopes: {
description: 'User security scopes.',
meta: 'property',
type: 'Array<string>'
},
userId: {
description: 'User id.',
meta: 'property',
type: 'string'
},
firstName: {
description: 'User first name.',
meta: 'property',
type: 'string'
},
lastName: {
description: 'User last name.',
meta: 'property',
type: 'string'
},
enabled: {
description: 'Whether is user enabled.',
meta: 'property',
type: 'boolean'
},
tenantId: {
description: 'Tenant id of the user.',
meta: 'property',
type: 'string'
},
customerId: {
description: 'Customer id of the user (available when user belongs to specific customer).',
meta: 'property',
type: 'string'
},
isPublic: {
description: 'Special flag indicating public user.',
meta: 'property',
type: 'boolean'
},
authority: {
description: 'User authority. Possible values: SYS_ADMIN, TENANT_ADMIN, CUSTOMER_USER',
meta: 'property',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/authority.enum.ts#L17">Authority</a>'
}
}
},
hideTitlePanel: {
description: 'Manages visibility of widget title panel. Useful for widget with custom title panels or different states. <b>updateWidgetParams()</b> function must be called after this property change.',
meta: 'property',

2
ui-ngx/src/app/shared/models/widget.models.ts

@ -462,6 +462,8 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
setEntityId?: boolean;
stateEntityParamName?: string;
mobileAction?: WidgetMobileActionDescriptor;
useShowWidgetActionFunction?: boolean;
showWidgetActionFunction?: string;
}
export interface WidgetComparisonSettings {

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3038,6 +3038,7 @@
"action-name-required": "Action name is required.",
"action-name-not-unique": "Another action with the same name already exists.<br/>Action name should be unique within the same action source.",
"action-icon": "Icon",
"use-show-widget-action-function": "Use show widget action function",
"action-type": "Type",
"action-type-required": "Action type is required.",
"edit-action": "Edit action",

Loading…
Cancel
Save