diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html index 79466e027a..e0cff4d312 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html @@ -44,7 +44,8 @@ (ngModelChange)="onActionUpdated()" [fillHeight]="true" [functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel']" - [validationArgs]="[]"> + [validationArgs]="[]" + [editorCompleter]="customPrettyActionEditorCompleter"> diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts index 52841cf2d0..fe552b7d1c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts @@ -35,6 +35,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { combineLatest } from 'rxjs'; import { CustomActionDescriptor } from '@shared/models/widget.models'; +import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; @Component({ selector: 'tb-custom-action-pretty-editor', @@ -63,6 +64,8 @@ export class CustomActionPrettyEditorComponent extends PageComponent implements @ViewChildren('rightPanel') rightPanelElmRef: QueryList>; + customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter; + private propagateChange = (_: any) => {}; constructor(protected store: Store) { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html index fd6c660fbc..442143855d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html @@ -94,7 +94,8 @@ [(ngModel)]="action.customFunction" (ngModelChange)="notifyActionUpdated()" [functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel']" - [validationArgs]="[]"> + [validationArgs]="[]" + [editorCompleter]="customPrettyActionEditorCompleter"> diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts index afdd2a476e..93b3a80a1b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts @@ -36,6 +36,7 @@ import * as ace from 'ace-builds'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { css_beautify, html_beautify } from 'js-beautify'; import { ResizeObserver } from '@juggle/resize-observer'; +import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; @Component({ selector: 'tb-custom-action-pretty-resources-tabs', @@ -70,6 +71,8 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl cssEditor: ace.Ace.Editor; setValuesPending = false; + customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter; + constructor(protected store: Store, private translate: TranslateService, private raf: RafService) { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts new file mode 100644 index 0000000000..6539780e02 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { TbEditorCompleter, TbEditorCompletions } from '@shared/models/ace/completion.models'; +import { widgetContextCompletions } from '@shared/models/ace/widget-completion.models'; +import { entityIdHref, entityTypeHref, serviceCompletions } from '@shared/models/ace/service-completion.models'; + +const customActionCompletions: TbEditorCompletions = { + ...{ + $event: { + meta: 'argument', + type: 'Event', + description: 'The DOM event that triggered this action.' + }, + widgetContext: widgetContextCompletions.ctx, + entityId: { + meta: 'argument', + type: entityIdHref, + description: 'Id of the entity for which the action was triggered.', + children: { + id: { + meta: 'property', + type: 'string', + description: 'UUID Id string' + }, + entityType: { + meta: 'property', + type: entityTypeHref, + description: 'Entity type' + } + } + }, + entityName: { + meta: 'argument', + type: 'string', + description: 'Name of the entity for which the action was triggered.' + }, + additionalParams: { + meta: 'argument', + type: 'object', + description: 'Optional object holding additional information.' + }, + entityLabel: { + meta: 'argument', + type: 'string', + description: 'Label of the entity for which the action was triggered.' + } + }, + ...serviceCompletions +}; + +const customPrettyActionCompletions: TbEditorCompletions = { + ...{ + htmlTemplate: { + meta: 'argument', + type: 'string', + description: 'HTML template used to render custom dialog.' + } + }, + ...customActionCompletions +} + +export const CustomActionEditorCompleter = new TbEditorCompleter(customActionCompletions); +export const CustomPrettyActionEditorCompleter = new TbEditorCompleter(customPrettyActionCompletions); diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index 7bf068a4d0..a61b3a80c6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -135,6 +135,7 @@ formControlName="customFunction" [functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" [validationArgs]="[]" + [editorCompleter]="customActionEditorCompleter" > diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index bdd4b9db18..3d7e9ae595 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -43,6 +43,7 @@ import { map, mergeMap, startWith, tap } from 'rxjs/operators'; import { DashboardService } from '@core/http/dashboard.service'; import { Dashboard } from '@shared/models/dashboard.models'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; export interface WidgetActionDialogData { isAdd: boolean; @@ -76,6 +77,8 @@ export class WidgetActionDialogComponent extends DialogComponent>; + customActionEditorCompleter = CustomActionEditorCompleter; + submitted = false; constructor(protected store: Store, diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index bf337967b9..3fdae9105d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -19,7 +19,7 @@ import { Inject, Injector, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { IDynamicWidgetComponent, WidgetContext } from '@home/models/widget-component.models'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; import { NotificationHorizontalPosition, @@ -27,6 +27,19 @@ import { NotificationVerticalPosition } from '@core/notification/notification.models'; import { FormBuilder, Validators } from '@angular/forms'; +import { DeviceService } from '@core/http/device.service'; +import { AssetService } from '@core/http/asset.service'; +import { EntityViewService } from '@core/http/entity-view.service'; +import { CustomerService } from '@core/http/customer.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { UserService } from '@core/http/user.service'; +import { AttributeService } from '@core/http/attribute.service'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { EntityService } from '@core/http/entity.service'; +import { DialogService } from '@core/services/dialog.service'; +import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { DatePipe } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { @@ -47,6 +60,21 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid @Inject('errorMessages') public readonly errorMessages: string[]) { super(store); this.ctx.$injector = $injector; + this.ctx.deviceService = $injector.get(DeviceService); + this.ctx.assetService = $injector.get(AssetService); + this.ctx.entityViewService = $injector.get(EntityViewService); + this.ctx.customerService = $injector.get(CustomerService); + this.ctx.dashboardService = $injector.get(DashboardService); + this.ctx.userService = $injector.get(UserService); + this.ctx.attributeService = $injector.get(AttributeService); + this.ctx.entityRelationService = $injector.get(EntityRelationService); + this.ctx.entityService = $injector.get(EntityService); + this.ctx.dialogs = $injector.get(DialogService); + this.ctx.customDialog = $injector.get(CustomDialogService); + this.ctx.date = $injector.get(DatePipe); + this.ctx.translate = $injector.get(TranslateService); + this.ctx.http = $injector.get(HttpClient); + this.ctx.$scope = this; if (this.ctx.defaultSubscription) { this.executingRpcRequest = this.ctx.defaultSubscription.executingRpcRequest; diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 2c786783e1..89867d0199 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -26,11 +26,19 @@ import { DatePipe } from '@angular/common'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { HttpClient } from '@angular/common/http'; +import { EntityViewService } from '@core/http/entity-view.service'; +import { CustomerService } from '@core/http/customer.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { UserService } from '@core/http/user.service'; export const ServicesMap = new Map>( [ ['deviceService', DeviceService], ['assetService', AssetService], + ['entityViewService', EntityViewService], + ['customerService', CustomerService], + ['dashboardService', DashboardService], + ['userService', UserService], ['attributeService', AttributeService], ['entityRelationService', EntityRelationService], ['entityService', EntityService], diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 1c371dc830..aa3f04bcad 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -43,7 +43,7 @@ import { WidgetSubscriptionApi } from '@core/api/widget-api.models'; import { ChangeDetectorRef, ComponentFactory, Injector, NgZone, Type } from '@angular/core'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { TenantId } from '@shared/models/id/tenant-id'; @@ -60,6 +60,19 @@ import { import { ActionNotificationShow } from '@core/notification/notification.actions'; import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { DeviceService } from '@core/http/device.service'; +import { AssetService } from '@core/http/asset.service'; +import { EntityViewService } from '@core/http/entity-view.service'; +import { CustomerService } from '@core/http/customer.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { UserService } from '@core/http/user.service'; +import { AttributeService } from '@core/http/attribute.service'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { EntityService } from '@core/http/entity.service'; +import { DialogService } from '@core/services/dialog.service'; +import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { DatePipe } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; export interface IWidgetAction { name: string; @@ -126,6 +139,21 @@ export class WidgetContext { } } + deviceService: DeviceService; + assetService: AssetService; + entityViewService: EntityViewService; + customerService: CustomerService; + dashboardService: DashboardService; + userService: UserService; + attributeService: AttributeService; + entityRelationService: EntityRelationService; + entityService: EntityService; + dialogs: DialogService; + customDialog: CustomDialogService; + date: DatePipe; + translate: TranslateService; + http: HttpClient; + private changeDetectorValue: ChangeDetectorRef; inited = false; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index d931bbd697..aa48420065 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -47,6 +47,7 @@ import { import { Subscription } from 'rxjs'; import { ResizeObserver } from '@juggle/resize-observer'; import Timeout = NodeJS.Timeout; +import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; // @dynamic @Component({ @@ -319,6 +320,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe this.jsEditor.on('change', () => { this.cleanupJsErrors(); }); + this.jsEditor.completers = [widgetEditorCompleter, ...(this.jsEditor.completers || [])]; this.setAceEditorValues(); } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts new file mode 100644 index 0000000000..913e566f34 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts @@ -0,0 +1,93 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { TbEditorCompleter, TbEditorCompletions } from '@shared/models/ace/completion.models'; +import { widgetContextCompletions } from '@shared/models/ace/widget-completion.models'; +import { serviceCompletions } from '@shared/models/ace/service-completion.models'; + +const widgetEditorCompletions: TbEditorCompletions = { + ... {self: { + description: 'Built-in variable self that is a reference to the widget instance', + type: 'WidgetTypeInstance', + meta: 'object', + children: { + ...{ + onInit: { + description: 'The first function which is called when widget is ready for initialization.
Should be used to prepare widget DOM, process widget settings and initial subscription information.', + meta: 'function' + }, + onDataUpdated: { + description: 'Called when the new data is available from the widget subscription.
Latest data can be accessed from ' + + 'the defaultSubscription property of widget context (ctx).', + meta: 'function' + }, + onResize: { + description: 'Called when widget container is resized. Latest width and height can be obtained from widget context (ctx).', + meta: 'function' + }, + onEditModeChanged: { + description: 'Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of widget context (ctx).', + meta: 'function' + }, + onMobileModeChanged: { + description: 'Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of widget context (ctx).', + meta: 'function' + }, + onDestroy: { + description: 'Called when widget element is destroyed. Should be used to cleanup all resources if necessary.', + meta: 'function' + }, + getSettingsSchema: { + description: 'Optional function returning widget settings schema json as alternative to Settings tab of Settings schema section.', + meta: 'function', + return: { + description: 'An widget settings schema json', + type: 'object' + } + }, + getDataKeySettingsSchema: { + description: 'Optional function returning particular data key settings schema json as alternative to Data key settings schema of Settings schema section.', + meta: 'function', + return: { + description: 'A particular data key settings schema json', + type: 'object' + } + }, + typeParameters: { + description: 'Returns object describing widget datasource parameters.', + meta: 'function', + return: { + description: 'An object describing widget datasource parameters.', + type: 'WidgetTypeParameters' + } + }, + actionSources: { + description: 'Returns map describing available widget action sources used to define user actions.', + meta: 'function', + return: { + description: 'A map of action sources by action source id.', + type: '{[actionSourceId: string]: WidgetActionSource}' + } + } + }, + ...widgetContextCompletions + } + }}, + ...widgetContextCompletions, + ...serviceCompletions +}; + +export const widgetEditorCompleter = new TbEditorCompleter(widgetEditorCompletions); diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index 0691aab813..72e30462e5 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -35,6 +35,8 @@ import { guid, isUndefined } from '@app/core/utils'; import { TranslateService } from '@ngx-translate/core'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { ResizeObserver } from '@juggle/resize-observer'; +import { TbEditorCompleter } from '@shared/models/ace/completion.models'; +import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; @Component({ selector: 'tb-js-func', @@ -77,6 +79,8 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @Input() fillHeight: boolean; + @Input() editorCompleter: TbEditorCompleter; + private noValidateValue: boolean; get noValidate(): boolean { return this.noValidateValue; @@ -153,6 +157,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, this.cleanupJsErrors(); this.updateView(); }); + if (this.editorCompleter) { + this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])]; + } this.editorResize$ = new ResizeObserver(() => { this.onAceEditorResize(); }); diff --git a/ui-ngx/src/app/shared/models/ace/completion.models.ts b/ui-ngx/src/app/shared/models/ace/completion.models.ts new file mode 100644 index 0000000000..f2810781bb --- /dev/null +++ b/ui-ngx/src/app/shared/models/ace/completion.models.ts @@ -0,0 +1,215 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import * as ace from 'ace-builds'; + +export type tbMetaType = 'object' | 'function' | 'service' | 'property' | 'argument'; + +export type TbEditorCompletions = {[name: string]: TbEditorCompletion}; + +export interface FunctionArgType { + type?: string; + description?: string; +} + +export interface FunctionArg extends FunctionArgType { + name: string; + type?: string; + description?: string; + optional?: boolean; +} + +export interface TbEditorCompletion { + meta: tbMetaType; + description?: string; + type?: string; + args?: FunctionArg[]; + return?: FunctionArgType; + children?: TbEditorCompletions; +} + +interface TbEditorAceCompletion extends ace.Ace.Completion { + isTbEditorAceCompletion: true; + snippet: string; + description?: string; + type?: string; + args?: FunctionArg[]; + return?: FunctionArgType; +} + +export class TbEditorCompleter implements ace.Ace.Completer { + + identifierRegexps: RegExp[] = [ + /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF.]/ + ]; + + constructor(private editorCompletions: TbEditorCompletions) { + } + + getCompletions(editor: ace.Ace.Editor, session: ace.Ace.EditSession, + position: ace.Ace.Point, prefix: string, callback: ace.Ace.CompleterCallback): void { + const result = this.prepareCompletions(prefix); + if (result) { + callback(null, result); + } + } + + private resolvePath(prefix: string): string[] { + if (!prefix || !prefix.length) { + return []; + } + let parts = prefix.split('.'); + if (parts.length) { + parts.pop(); + } else { + parts = []; + } + let currentCompletions = this.editorCompletions; + for (const part of parts) { + if (currentCompletions[part]) { + currentCompletions = currentCompletions[part].children; + if (!currentCompletions) { + return null; + } + } else { + return null; + } + } + return parts; + } + + private prepareCompletions(prefix: string): ace.Ace.Completion[] { + const path = this.resolvePath(prefix); + if (path !== null) { + return this.toAceCompletionsList(this.editorCompletions, path); + } else { + return []; + } + } + + private toAceCompletionsList(completions: TbEditorCompletions, parentPath: string[]): ace.Ace.Completion[] { + const result: ace.Ace.Completion[] = []; + let targetCompletions = completions; + let parentPrefix = ''; + if (parentPath.length) { + parentPrefix = parentPath.join('.') + '.'; + for (const path of parentPath) { + targetCompletions = targetCompletions[path].children; + } + } + for (const key of Object.keys(targetCompletions)) { + result.push(this.toAceCompletion(key, targetCompletions[key], parentPrefix)); + } + return result; + } + + private toAceCompletion(name: string, completion: TbEditorCompletion, parentPrefix: string): ace.Ace.Completion { + const aceCompletion: TbEditorAceCompletion = { + isTbEditorAceCompletion: true, + snippet: parentPrefix + name, + name, + caption: parentPrefix + name, + score: 100000, + value: parentPrefix + name, + meta: completion.meta, + type: completion.type, + description: completion.description, + args: completion.args, + return: completion.return + }; + return aceCompletion; + } + + getDocTooltip(completion: TbEditorAceCompletion) { + if (completion && completion.isTbEditorAceCompletion) { + return { + docHTML: this.createDocHTML(completion) + }; + } + } + + private createDocHTML(completion: TbEditorAceCompletion): string { + let title = `${completion.name}`; + if (completion.meta === 'function') { + title += '('; + if (completion.args) { + const strArgs: string[] = []; + for (const arg of completion.args) { + let strArg = `${arg.name}`; + if (arg.optional) { + strArg += '?'; + } + if (arg.type) { + strArg += `: ${arg.type}`; + } + strArgs.push(strArg); + } + title += strArgs.join(', '); + } + title += '): '; + if (completion.return) { + title += completion.return.type; + } else { + title += 'void'; + } + } else { + title += `: ${completion.type ? completion.type : completion.meta}`; + } + let html = `
${title}`; + if (completion.description) { + html += `
${completion.description}
`; + } + if (completion.args || completion.return) { + let functionInfoBlock = '
'; + if (completion.args) { + functionInfoBlock += '
Parameters
' + let argsTable = ''; + const strArgs: string[] = []; + for (const arg of completion.args) { + let strArg = `'; + strArgs.push(strArg); + } + argsTable += strArgs.join('') + '
${arg.name}`; + if (arg.optional) { + strArg += ' (optional)'; + } + strArg += ''; + if (arg.type) { + strArg += `${arg.type}` + } + strArg += ''; + if (arg.description) { + strArg += `${arg.description}`; + } + strArg += '
'; + functionInfoBlock += argsTable; + } + if (completion.return) { + let returnStr = '
Returns
'; + returnStr += `
${completion.return.type}`; + if (completion.return.description) { + returnStr += `: ${completion.return.description}`; + } + returnStr += '
'; + functionInfoBlock += returnStr; + } + functionInfoBlock += '
'; + html += functionInfoBlock; + } + html += '
'; + return html; + } +} diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts new file mode 100644 index 0000000000..9f4b391152 --- /dev/null +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -0,0 +1,346 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { FunctionArg, FunctionArgType, TbEditorCompletions } from '@shared/models/ace/completion.models'; + +export const entityIdHref = 'EntityId'; + +export const entityTypeHref = 'EntityType'; + +export const pageDataHref = 'PageData'; + +export const deviceInfoHref = 'DeviceInfo'; + +export const deviceHref = 'Device'; + +export const deviceCredentialsHref = 'DeviceCredentials'; + +export const pageLinkArg: FunctionArg = { + name: 'pageLink', + type: 'PageLink', + description: 'Page link object used to perform paginated request.' +}; + +export const requestConfigArg: FunctionArg = { + name: 'config', + type: 'RequestConfig', + description: 'HTTP request configuration.', + optional: true +}; + +export function observableReturnType(objectType: string): FunctionArgType { + return { + type: `Observable<${objectType}>`, + description: `An Observable of ${objectType} object.` + }; +} + +export function observableVoid(): FunctionArgType { + return { + type: `Observable<void>`, + description: `An Observable.` + }; +} + +export function observableArrayReturnType(objectType: string): FunctionArgType { + return { + type: `Observable<Array<${objectType}>>`, + description: `An Observable of array of ${objectType} objects.` + }; +} + +export function observablePageDataReturnType(objectType: string): FunctionArgType { + return { + type: `Observable<${pageDataHref}<${objectType}>>`, + description: `An Observable of page result as a ${pageDataHref} holding array of ${objectType} objects.` + }; +} + +export const serviceCompletions: TbEditorCompletions = { + deviceService: { + description: 'Device Service API
' + + 'See DeviceService for API reference.', + meta: 'service', + type: 'DeviceService', + children: { + getTenantDeviceInfos: { + description: 'Get tenant devices', + meta: 'function', + args: [ + pageLinkArg, + { name: 'type', type: 'string', optional: true, description: 'Device type'}, + requestConfigArg + ], + return: observablePageDataReturnType(deviceInfoHref) + }, + getCustomerDeviceInfos: { + description: 'Get customer devices', + meta: 'function', + args: [ + { name: 'customerId', type: 'string', description: 'Id of the customer'}, + pageLinkArg, + { name: 'type', type: 'string', optional: true, description: 'Device type'}, + requestConfigArg + ], + return: observablePageDataReturnType(deviceInfoHref) + }, + getDevice: { + description: 'Get device by id', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableReturnType(deviceHref) + }, + getDevices: { + description: 'Get devices by ids', + meta: 'function', + args: [ + { name: 'deviceIds', type: 'Array', description: 'List of device ids'}, + requestConfigArg + ], + return: observableArrayReturnType(deviceHref) + }, + getDeviceInfo: { + description: 'Get device info by id', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableReturnType(deviceInfoHref) + }, + saveDevice: { + description: 'Save device', + meta: 'function', + args: [ + { name: 'device', type: deviceHref, description: 'Device object to save'}, + requestConfigArg + ], + return: observableReturnType(deviceHref) + }, + deleteDevice: { + description: 'Delete device by id', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableVoid() + }, + getDeviceTypes: { + description: 'Get all available devices types', + meta: 'function', + args: [ + requestConfigArg + ], + return: observableArrayReturnType('EntitySubtype') + }, + getDeviceCredentials: { + description: 'Get device credentials by device id', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + { name: 'sync', type: 'boolean', description: 'Whether to execute HTTP request synchronously (false by default)', optional: true}, + requestConfigArg + ], + return: observableReturnType(deviceCredentialsHref) + }, + saveDeviceCredentials: { + description: 'Save device credentials', + meta: 'function', + args: [ + { name: 'deviceCredentials', type: deviceCredentialsHref, description: 'Device credentials object to save'}, + requestConfigArg + ], + return: observableReturnType(deviceCredentialsHref) + }, + makeDevicePublic: { + description: 'Make device public (available from public dashboard)', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableReturnType(deviceHref) + }, + assignDeviceToCustomer: { + description: 'Assign device to specific customer', + meta: 'function', + args: [ + { name: 'customerId', type: 'string', description: 'Id of the customer'}, + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableReturnType(deviceHref) + }, + unassignDeviceFromCustomer: { + description: 'Unassign device from any customer', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + requestConfigArg + ], + return: observableVoid() + }, + sendOneWayRpcCommand: { + description: 'Send one way (without response) RPC command to the device.', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + { name: 'requestBody', type: 'object', description: 'Request body to be sent to device'}, + requestConfigArg + ], + return: { + type: `Observable<any>`, + description: `A command execution Observable.` + } + }, + sendTwoWayRpcCommand: { + description: 'Sends two way (with response) RPC command to the device.', + meta: 'function', + args: [ + { name: 'deviceId', type: 'string', description: 'Id of the device'}, + { name: 'requestBody', type: 'object', description: 'Request body to be sent to device'}, + requestConfigArg + ], + return: { + type: `Observable<any>`, + description: `A command execution Observable of response body.` + } + }, + findByQuery: { + description: 'Find devices by search query', + meta: 'function', + args: [ + { name: 'query', type: 'DeviceSearchQuery', + description: 'Device search query object'}, + requestConfigArg + ], + return: observableArrayReturnType(deviceHref) + }, + findByName: { + description: 'Find device by name', + meta: 'function', + args: [ + { name: 'deviceName', type: 'string', + description: 'Search device name'}, + requestConfigArg + ], + return: observableReturnType(deviceHref) + }, + claimDevice: { + description: 'Send claim device request', + meta: 'function', + args: [ + { name: 'deviceName', type: 'string', + description: 'Claiming device name'}, + requestConfigArg + ], + return: observableReturnType('ClaimResult') + }, + unclaimDevice: { + description: 'Send un-claim device request', + meta: 'function', + args: [ + { name: 'deviceName', type: 'string', + description: 'Device name to un-claim'}, + requestConfigArg + ], + return: observableVoid() + } + } + }, + assetService: { + description: 'Asset Service API
' + + 'See AssetService for API reference.', + meta: 'service', + type: 'AssetService' + }, + entityViewService: { + description: 'EntityView Service API
' + + 'See EntityViewService for API reference.', + meta: 'service', + type: 'EntityViewService' + }, + customerService: { + description: 'Customer Service API
' + + 'See CustomerService for API reference.', + meta: 'service', + type: 'CustomerService' + }, + dashboardService: { + description: 'Dashboard Service API
' + + 'See DashboardService for API reference.', + meta: 'service', + type: 'DashboardService' + }, + userService: { + description: 'User Service API
' + + 'See UserService for API reference.', + meta: 'service', + type: 'UserService' + }, + attributeService: { + description: 'Attribute Service API
' + + 'See AttributeService for API reference.', + meta: 'service', + type: 'AttributeService' + }, + entityRelationService: { + description: 'Entity Relation Service API
' + + 'See EntityRelationService for API reference.', + meta: 'service', + type: 'EntityRelationService' + }, + entityService: { + description: 'Entity Service API
' + + 'See EntityService for API reference.', + meta: 'service', + type: 'EntityService' + }, + dialogs: { + description: 'Dialogs Service API
' + + 'See DialogService for API reference.', + meta: 'service', + type: 'DialogService' + }, + customDialog: { + description: 'Custom Dialog Service API
' + + 'See CustomDialogService for API reference.', + meta: 'service', + type: 'CustomDialogService' + }, + date: { + description: 'Date Pipe
Formats a date value according to locale rules.
' + + 'See DatePipe for API reference.', + meta: 'service', + type: 'DatePipe' + }, + translate: { + description: 'Translate Service API
' + + 'See TranslateService for API reference.', + meta: 'service', + type: 'TranslateService' + }, + http: { + description: 'HTTP Client Service
' + + 'See HttpClient for API reference.', + meta: 'service', + type: 'HttpClient' + } +} diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts new file mode 100644 index 0000000000..6f519410a5 --- /dev/null +++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts @@ -0,0 +1,615 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models'; +import { entityIdHref, serviceCompletions } from '@shared/models/ace/service-completion.models'; + +export const timewindowCompletion: TbEditorCompletion = { + description: 'Timewindow configuration object', + meta: 'property', + type: 'Timewindow', + children: { + displayValue: { + description: 'Current timewindow display value.', + meta: 'property', + type: 'string' + }, + hideInterval: { + description: 'Whether to hide interval selection in timewindow panel.', + meta: 'property', + type: 'boolean' + }, + hideAggregation: { + description: 'Whether to hide aggregation selection in timewindow panel.', + meta: 'property', + type: 'boolean' + }, + hideAggInterval: { + description: 'Whether to hide aggregation interval selection in timewindow panel.', + meta: 'property', + type: 'boolean' + }, + selectedTab: { + description: 'Current selected timewindow type (0 - realtime, 1 - history).', + meta: 'property', + type: 'number' + }, + realtime: { + description: 'Realtime timewindow configuration object.', + meta: 'property', + type: 'IntervalWindow', + children: { + interval: { + description: 'Timewindow aggregation interval in milliseconds', + meta: 'property', + type: 'number' + }, + timewindowMs: { + description: 'Timewindow interval in milliseconds', + meta: 'property', + type: 'number' + } + } + }, + history: { + description: 'History timewindow configuration object.', + meta: 'property', + type: 'HistoryWindow', + children: { + historyType: { + description: 'History timewindow type (0 - last interval, 1 - fixed)', + meta: 'property', + type: 'number' + }, + interval: { + description: 'Timewindow aggregation interval in milliseconds', + meta: 'property', + type: 'number' + }, + timewindowMs: { + description: 'Timewindow interval in milliseconds', + meta: 'property', + type: 'number' + }, + fixedTimewindow: { + description: 'Fixed history timewindow configuration object', + meta: 'property', + type: 'FixedWindow', + children: { + startTimeMs: { + description: 'Timewindow start time in UTC milliseconds', + meta: 'property', + type: 'number' + }, + endTimeMs: { + description: 'Timewindow end time in UTC milliseconds', + meta: 'property', + type: 'number' + } + } + } + } + }, + aggregation: { + description: 'Timewindow aggregation configuration object.', + meta: 'property', + type: 'Aggregation', + children: { + interval: { + description: 'Aggregation interval in milliseconds', + meta: 'property', + type: 'number' + }, + type: { + description: 'Aggregation type', + meta: 'property', + type: 'AggregationType' + }, + limit: { + description: 'Maximum allowed datapoints when aggregation is disabled (AggregationType == \'NONE\')', + meta: 'property', + type: 'number' + } + } + } + } +} + +export const widgetContextCompletions: TbEditorCompletions = { + ctx: { + description: 'A reference to widget context that has all necessary API
and data used by widget instance.', + meta: 'object', + type: 'WidgetContext', + children: { + ...{ + $container: { + description: 'Container element of the widget.
Can be used to dynamically access or modify widget DOM using jQuery API.', + meta: 'property', + type: 'jQuery Object' + }, + $scope: { + description: 'Reference to the current widget component.
Can be used to access/modify component properties when widget is built using Angular approach.', + meta: 'property', + type: 'IDynamicWidgetComponent' + }, + width: { + description: 'Current width of widget container in pixels.', + meta: 'property', + type: 'number' + }, + height: { + description: 'Current height of widget container in pixels.', + meta: 'property', + type: 'number' + }, + isEdit: { + description: 'Indicates whether the dashboard is in in the view or editing state.', + meta: 'property', + type: 'boolean' + }, + isMobile: { + description: 'Indicates whether the dashboard view is less then 960px width (default mobile breakpoint).', + meta: 'property', + type: 'boolean' + }, + widgetConfig: { + description: 'Common widget configuration containing properties such as color (text color), backgroundColor (widget background color), etc.', + meta: 'property', + type: 'WidgetConfig', + children: { + title: { + description: 'Widget title.', + meta: 'property', + type: 'string' + }, + titleIcon: { + description: 'Widget title icon.', + meta: 'property', + type: 'string' + }, + showTitle: { + description: 'Whether to show widget title.', + meta: 'property', + type: 'boolean' + }, + showTitleIcon: { + description: 'Whether to show widget title icon.', + meta: 'property', + type: 'boolean' + }, + iconColor: { + description: 'Widget title icon color.', + meta: 'property', + type: 'string' + }, + iconSize: { + description: 'Widget title icon size.', + meta: 'property', + type: 'string' + }, + titleTooltip: { + description: 'Widget title tooltip content.', + meta: 'property', + type: 'string' + }, + dropShadow: { + description: 'Enable/disable widget card shadow.', + meta: 'property', + type: 'boolean' + }, + enableFullscreen: { + description: 'Whether to enable fullscreen button on widget.', + meta: 'property', + type: 'boolean' + }, + useDashboardTimewindow: { + description: 'Whether to use dashboard timewindow (applicable for timeseries widgets).', + meta: 'property', + type: 'boolean' + }, + displayTimewindow: { + description: 'Whether to display timewindow (applicable for timeseries widgets).', + meta: 'property', + type: 'boolean' + }, + showLegend: { + description: 'Whether to show legend.', + meta: 'property', + type: 'boolean' + }, + legendConfig: { + description: 'Legend configuration.', + meta: 'property', + type: 'LegendConfig', + children: { + position: { + description: 'Legend position. Possible values: \'top\', \'bottom\', \'left\', \'right\'', + meta: 'property', + type: 'LegendPosition', + }, + direction: { + description: 'Legend direction. Possible values: \'column\', \'row\'', + meta: 'property', + type: 'LegendDirection', + }, + showMin: { + description: 'Whether to display aggregated min values.', + meta: 'property', + type: 'boolean', + }, + showMax: { + description: 'Whether to display aggregated max values.', + meta: 'property', + type: 'boolean', + }, + showAvg: { + description: 'Whether to display aggregated average values.', + meta: 'property', + type: 'boolean', + }, + showTotal: { + description: 'Whether to display aggregated total values.', + meta: 'property', + type: 'boolean', + } + } + }, + timewindow: timewindowCompletion, + mobileHeight: { + description: 'Widget height in mobile mode.', + meta: 'property', + type: 'number' + }, + mobileOrder: { + description: 'Widget order in mobile mode.', + meta: 'property', + type: 'number' + }, + color: { + description: 'Widget text color.', + meta: 'property', + type: 'string' + }, + backgroundColor: { + description: 'Widget background color.', + meta: 'property', + type: 'string' + }, + padding: { + description: 'Widget card padding.', + meta: 'property', + type: 'string' + }, + margin: { + description: 'Widget card margin.', + meta: 'property', + type: 'string' + }, + widgetStyle: { + description: 'Widget element style object.', + meta: 'property', + type: 'object' + }, + titleStyle: { + description: 'Widget title element style object.', + meta: 'property', + type: 'object' + }, + units: { + description: 'Optional property defining units text of values displayed by widget. Useful for simple widgets like cards or gauges.', + meta: 'property', + type: 'string' + }, + decimals: { + description: 'Optional property defining how many positions should be used to display decimal part of the value number.', + meta: 'property', + type: 'number' + }, + actions: { + description: 'Map of configured widget actions.', + meta: 'property', + type: 'object' + }, + settings: { + description: 'Object holding widget settings according to widget type.', + meta: 'property', + type: 'object' + }, + alarmSource: { + description: 'Configured alarm source for alarm widget type.', + meta: 'property', + type: 'Datasource' + }, + alarmSearchStatus: { + description: 'Configured default alarm search status for alarm widget type.', + meta: 'property', + type: 'AlarmSearchStatus' + }, + alarmsPollingInterval: { + description: 'Configured alarms polling interval for alarm widget type.', + meta: 'property', + type: 'number' + }, + alarmsMaxCountLoad: { + description: 'Configured maximum alarms to load for alarm widget type.', + meta: 'property', + type: 'number' + }, + alarmsFetchSize: { + description: 'Configured alarms page size used to load alarms.', + meta: 'property', + type: 'number' + }, + datasources: { + description: 'Array of configured widget datasources.', + meta: 'property', + type: 'Array<Datasource>' + } + } + }, + settings: { + description: 'Widget settings containing widget specific properties according to the defined settings json schema', + meta: 'property', + type: 'object' + }, + units: { + description: 'Optional property defining units text of values displayed by widget. Useful for simple widgets like cards or gauges.', + meta: 'property', + type: 'string' + }, + decimals: { + description: 'Optional property defining how many positions should be used to display decimal part of the value number.', + meta: 'property', + type: 'number' + }, + hideTitlePanel: { + description: 'Manages visibility of widget title panel. Useful for widget with custom title panels or different states.', + meta: 'property', + type: 'boolean' + }, + defaultSubscription: { + description: 'Default widget subscription object contains all subscription information,
including current data, according to the widget type.', + meta: 'property', + type: 'IWidgetSubscription' + }, + timewindowFunctions: { + description: 'Object with timewindow functions used to manage widget data time frame. Can by used by Time-series or Alarm widgets.', + meta: 'property', + type: 'TimewindowFunctions', + children: { + onUpdateTimewindow: { + description: 'This function can be used to update current subscription time frame
to historical one identified by startTimeMs and endTimeMs arguments.', + meta: 'function', + args: [ + { + name: 'startTimeMs', + description: 'Timewindow start time in UTC milliseconds', + type: 'number' + }, + { + name: 'endTimeMs', + description: 'Timewindow end time in UTC milliseconds', + type: 'number' + } + ] + }, + onResetTimewindow: { + description: 'Resets subscription time frame to default defined by widget timewindow component
or dashboard timewindow depending on widget settings.', + meta: 'function' + } + } + }, + controlApi: { + description: 'Object that provides API functions for RPC (Control) widgets.', + meta: 'property', + type: 'RpcApi', + children: { + sendOneWayCommand: { + description: 'Sends one way (without response) RPC command to the device.', + meta: 'function', + args: [ + { + name: 'method', + description: 'RPC method name', + type: 'string' + }, + { + name: 'params', + description: 'RPC method params, custom json object', + type: 'object', + optional: true + }, + { + name: 'timeout', + description: 'Maximum delay in milliseconds to wait until response/acknowledgement is received.', + type: 'number', + optional: true + } + ], + return: { + description: 'A command execution Observable.', + type: 'Observable<any>' + } + }, + sendTwoWayCommand: { + description: 'Sends two way (with response) RPC command to the device.', + meta: 'function', + args: [ + { + name: 'method', + description: 'RPC method name', + type: 'string' + }, + { + name: 'params', + description: 'RPC method params, custom json object', + type: 'object', + optional: true + }, + { + name: 'timeout', + description: 'Maximum delay in milliseconds to wait until response/acknowledgement is received.', + type: 'number', + optional: true + } + ], + return: { + description: 'A command execution Observable of response body.', + type: 'Observable<any>' + } + } + } + }, + actionsApi: { + description: 'Set of API functions to work with user defined actions.', + meta: 'property', + type: 'WidgetActionsApi', + children: { + getActionDescriptors: { + description: 'Get list of action descriptors for provided actionSourceId.', + meta: 'function', + args: [ + { + name: 'actionSourceId', + description: 'Id of widget action source', + type: 'string' + } + ], + return: { + description: 'The list of action descriptors', + type: 'Array<WidgetActionDescriptor>' + } + }, + handleWidgetAction: { + description: 'Handle action produced by particular action source.', + meta: 'function', + args: [ + { + name: '$event', + description: 'DOM event object associated with action.', + type: 'Event' + }, + { + name: 'descriptor', + description: 'An action descriptor.', + type: 'WidgetActionDescriptor' + }, + { + name: 'entityId', + description: 'Current entity id provided by action source if available.', + type: entityIdHref, + optional: true + }, + { + name: 'entityName', + description: 'Current entity name provided by action source if available.', + type: 'string', + optional: true + } + ] + } + } + }, + stateController: { + description: 'Reference to Dashboard state controller, providing API to manage current dashboard state.', + meta: 'property', + type: 'IStateController', + children: { + openState: { + description: 'Navigate to new dashboard state.', + meta: 'function', + args: [ + { + name: 'id', + description: 'An id of the target dashboard state.', + type: 'string' + }, + { + name: 'params', + description: 'An object with state parameters to use by the new state.', + type: 'StateParams', + optional: true + }, + { + name: 'openRightLayout', + description: 'An optional boolean argument to force open right dashboard layout if present in mobile view mode.', + type: 'boolean', + optional: true + } + ] + }, + updateState: { + description: 'Updates current dashboard state.', + meta: 'function', + args: [ + { + name: 'id', + description: 'An optional id of the target dashboard state to replace current state id.', + type: 'string', + optional: true + }, + { + name: 'params', + description: 'An object with state parameters to update current state parameters.', + type: 'StateParams', + optional: true + }, + { + name: 'openRightLayout', + description: 'An optional boolean argument to force open right dashboard layout if present in mobile view mode.', + type: 'boolean', + optional: true + } + ] + }, + getStateId: { + description: 'Get current dashboard state id.', + meta: 'function', + return: { + description: 'current dashboard state id.', + type: 'string' + } + }, + getStateParams: { + description: 'Get current dashboard state parameters.', + meta: 'function', + return: { + description: 'current dashboard state parameters.', + type: 'StateParams' + } + }, + getStateParamsByStateId: { + description: 'Get state parameters for particular dashboard state identified by id.', + meta: 'function', + args: [ + { + name: 'id', + description: 'An id of the target dashboard state.', + type: 'string' + } + ], + return: { + description: 'current dashboard state parameters.', + type: 'StateParams' + } + } + } + } + }, + ...serviceCompletions + } + } +} diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ba963eb075..494a5f026b 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -416,6 +416,58 @@ mat-label { } } +.tb-ace-doc-tooltip { + code { + color: #444; + &.title { + font-size: 14px; + } + } + div.tb-function-info { + font-size: 14px; + } + div.tb-function-return { + font-size: 1rem; + letter-spacing: 0.03rem; + color: #444; + code { + font-size: 14px; + letter-spacing: normal; + } + } + div.tb-api-title { + font-weight: bold; + font-size: 16px; + color: #6e6e6e; + padding-top: 12px; + padding-bottom: 12px; + } + table.tb-api-table { + width: 100%; + border-collapse: collapse; + tr { + border-bottom: 1px solid #a8a8a8; + &:last-child { + border-bottom: none; + } + td { + font-size: 14px; + line-height: 1.6rem; + &:first-child { + font-weight: 600; + padding-left: 16px; + width: 20%; + } + &.arg-description { + font-size: 1rem; + letter-spacing: .03rem; + color: #444; + } + } + } + } +} + .tb-default, .tb-dark { /*********************************