diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts index c77065fbf8..61fdcb9b58 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts @@ -61,13 +61,13 @@ export interface IotSvgContext { properties: {[id: string]: any}; } -export type IotSvgStateRenderFunction = (svg: Svg, ctx: IotSvgContext) => void; +export type IotSvgStateRenderFunction = (ctx: IotSvgContext, svg: Svg) => void; -export type IotSvgTagStateRenderFunction = (element: Element, ctx: IotSvgContext) => void; +export type IotSvgTagStateRenderFunction = (ctx: IotSvgContext, element: Element) => void; export type IotSvgActionTrigger = 'click'; -export type IotSvgActionFunction = (event: Event, ctx: IotSvgContext) => void; +export type IotSvgActionFunction = (ctx: IotSvgContext, event: Event) => void; export interface IotSvgAction { actionFunction?: string; action?: IotSvgActionFunction; @@ -421,13 +421,13 @@ export class IotSvgObject { } private prepareMetadata() { - this.metadata.stateRender = parseFunction(this.metadata.stateRenderFunction, ['svg', 'ctx']) || (() => {}); + this.metadata.stateRender = parseFunction(this.metadata.stateRenderFunction, ['ctx', 'svg']) || (() => {}); for (const tag of this.metadata.tags) { - tag.stateRender = parseFunction(tag.stateRenderFunction, ['element', 'ctx']) || (() => {}); + tag.stateRender = parseFunction(tag.stateRenderFunction, ['ctx', 'element']) || (() => {}); if (tag.actions) { for (const trigger of Object.keys(tag.actions)) { const action = tag.actions[trigger]; - action.action = parseFunction(action.actionFunction, ['event', 'ctx']) || (() => {}); + action.action = parseFunction(action.actionFunction, ['ctx', 'event']) || (() => {}); } } } @@ -483,7 +483,7 @@ export class IotSvgObject { elements.forEach(element => { element.attr('cursor', 'pointer'); element.on(trigger, (event) => { - action.action(event, this.context); + action.action(this.context, event); }); }); } @@ -637,11 +637,11 @@ export class IotSvgObject { } private renderState(): void { - this.metadata.stateRender(this.svgShape, this.context); + this.metadata.stateRender(this.context, this.svgShape); for (const tag of this.metadata.tags) { const elements = this.context.tags[tag.tag];// this.svgShape.find(`[tb\\:tag="${tag.tag}"]`); elements.forEach(element => { - tag.stateRender(element, this.context); + tag.stateRender(this.context, element); }); } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html index 01914d1e32..88302152c5 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html @@ -49,7 +49,10 @@ @@ -65,7 +68,10 @@ diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts index 663a87d8fa..7b5e36266b 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts @@ -38,6 +38,13 @@ import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models'; import { MatExpansionPanel } from '@angular/material/expansion'; import { JsFuncComponent } from '@shared/components/js-func.component'; import { MatSelect } from '@angular/material/select'; +import { TbEditorCompleter } from '@shared/models/ace/completion.models'; +import { + scadaSymbolClickActionHighlightRules, + scadaSymbolClickActionPropertiesHighlightRules, + scadaSymbolElementStateRenderHighlightRules, + scadaSymbolElementStateRenderPropertiesHighlightRules +} from '@home/pages/scada-symbol/scada-symbol.models'; @Component({ selector: 'tb-scada-symbol-metadata-tag', @@ -78,6 +85,12 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On @Input() tags: string[]; + @Input() + elementStateRenderFunctionCompleter: TbEditorCompleter; + + @Input() + clickActionFunctionCompleter: TbEditorCompleter; + @Output() tagRemoved = new EventEmitter(); @@ -87,6 +100,12 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On modelValue: IotSvgTag; + scadaSymbolElementStateRenderHighlightRules = scadaSymbolElementStateRenderHighlightRules; + scadaSymbolElementStateRenderPropertiesHighlightRules = scadaSymbolElementStateRenderPropertiesHighlightRules; + + scadaSymbolClickActionHighlightRules = scadaSymbolClickActionHighlightRules; + scadaSymbolClickActionPropertiesHighlightRules = scadaSymbolClickActionPropertiesHighlightRules; + private propagateChange = (_val: any) => {}; constructor(private fb: UntypedFormBuilder, diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html index 5e3b49a4aa..6ce57301af 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html @@ -19,6 +19,8 @@
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts index 5b6c8210db..3d77c90d0b 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts @@ -40,6 +40,7 @@ import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolMetadataTagComponent } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component'; +import { TbEditorCompleter } from '@shared/models/ace/completion.models'; const tagValidator = (control: AbstractControl): ValidationErrors | null => { const tag: IotSvgTag = control.value; @@ -83,6 +84,12 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O @Input() tags: string[]; + @Input() + elementStateRenderFunctionCompleter: TbEditorCompleter; + + @Input() + clickActionFunctionCompleter: TbEditorCompleter; + availableTags: string[]; tagsFormGroup: UntypedFormGroup; diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html index 134dc9bb06..e9fb61bc73 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html @@ -43,7 +43,10 @@ @@ -55,6 +58,8 @@
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss index 7b923e1688..75ab006bc0 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss @@ -73,4 +73,40 @@ } } } + .ace_tb { + &.ace_scada-symbol { + &-ctx { + color: orangered; + font-weight: bold; + &-properties, &-property { + color: #0c959c; + } + &-values, &-value { + color: #669900; + } + &-tags, &-tag { + color: #cc8500; + } + &-api, &-api-method { + color: #ff00fe; + } + &-properties, &-values, &-tags, &-api { + font-style: italic; + // font-weight: bold; + } + &-property, &-value, &-tag, &-api-method { + font-weight: bold; + // font-weight: bold; + } + } + &-svg, &-element, &-event { + color: orangered; + font-weight: bold; + &-properties { + color: #0c959c; + font-weight: bold; + } + } + } + } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts index 75e6fcc8bb..fba374b848 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts @@ -14,7 +14,16 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + Component, + forwardRef, + Input, + OnChanges, + OnInit, + SimpleChanges, + ViewChild, + ViewEncapsulation +} from '@angular/core'; import { ControlValueAccessor, NG_VALIDATORS, @@ -26,7 +35,7 @@ import { Validators } from '@angular/forms'; import { PageComponent } from '@shared/components/page.component'; -import { IotSvgMetadata } from '@home/components/widget/lib/svg/iot-svg.models'; +import { emptyMetadata, IotSvgMetadata } from '@home/components/widget/lib/svg/iot-svg.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; @@ -34,6 +43,16 @@ import { TranslateService } from '@ngx-translate/core'; import { ScadaSymbolMetadataTagsComponent } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component'; +import { TbEditorCompleter } from '@shared/models/ace/completion.models'; +import { + clickActionFunctionCompletions, + elementStateRenderFunctionCompletions, + generalStateRenderFunctionCompletions, + iotSvgContextCompletion, + scadaSymbolGeneralStateRenderHighlightRules, + scadaSymbolGeneralStateRenderPropertiesHighlightRules +} from '@home/pages/scada-symbol/scada-symbol.models'; +import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; @Component({ selector: 'tb-scada-symbol-metadata', @@ -53,7 +72,7 @@ import { ], encapsulation: ViewEncapsulation.None }) -export class ScadaSymbolMetadataComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator { +export class ScadaSymbolMetadataComponent extends PageComponent implements OnInit, OnChanges, ControlValueAccessor, Validator { @ViewChild('symbolMetadataTags') symbolMetadataTags: ScadaSymbolMetadataTagsComponent; @@ -91,9 +110,18 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni selectedOption = 'general'; + generalStateRenderFunctionCompleter: TbEditorCompleter; + elementStateRenderFunctionCompleter: TbEditorCompleter; + clickActionFunctionCompleter: TbEditorCompleter; + + scadaSymbolGeneralStateRenderHighlightRules = scadaSymbolGeneralStateRenderHighlightRules; + + scadaSymbolGeneralStateRenderPropertiesHighlightRules = scadaSymbolGeneralStateRenderPropertiesHighlightRules; + constructor(protected store: Store, private fb: UntypedFormBuilder, - private translate: TranslateService) { + private translate: TranslateService, + private customTranslate: CustomTranslatePipe) { super(store); } @@ -105,12 +133,24 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni behavior: [null], properties: [null] }); + this.updateFunctionCompleters(emptyMetadata()); this.metadataFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); } + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (['tags'].includes(propName)) { + this.updateFunctionCompleters(this.modelValue); + } + } + } + } + registerOnChange(fn: any): void { this.propagateChange = fn; } @@ -132,6 +172,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni this.metadataFormGroup.patchValue( value, {emitEvent: false} ); + this.updateFunctionCompleters(value); } editTagStateRenderFunction(tag: string): void { @@ -157,5 +198,28 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue(); this.modelValue = metadata; this.propagateChange(this.modelValue); + this.updateFunctionCompleters(metadata); + } + + private updateFunctionCompleters(metadata: IotSvgMetadata) { + const contextCompleter = iotSvgContextCompletion(metadata, this.tags, this.customTranslate); + const generalStateRender = generalStateRenderFunctionCompletions(contextCompleter); + if (!this.generalStateRenderFunctionCompleter) { + this.generalStateRenderFunctionCompleter = new TbEditorCompleter(generalStateRender); + } else { + this.generalStateRenderFunctionCompleter.updateCompletions(generalStateRender); + } + const elementStateRender = elementStateRenderFunctionCompletions(contextCompleter); + if (!this.elementStateRenderFunctionCompleter) { + this.elementStateRenderFunctionCompleter = new TbEditorCompleter(elementStateRender); + } else { + this.elementStateRenderFunctionCompleter.updateCompletions(elementStateRender); + } + const clickAction = clickActionFunctionCompletions(contextCompleter); + if (!this.clickActionFunctionCompleter) { + this.clickActionFunctionCompleter = new TbEditorCompleter(clickAction); + } else { + this.clickActionFunctionCompleter.updateCompletions(clickAction); + } } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts index 3cb586218b..421ddce343 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts @@ -24,11 +24,22 @@ import { setupAddTagPanelTooltip, setupTagPanelTooltip } from '@home/pages/scada-symbol/scada-symbol-tooltip.components'; -import { iotSvgContentData } from '@home/components/widget/lib/svg/iot-svg.models'; +import { + IotSvgBehavior, + IotSvgBehaviorType, + iotSvgContentData, + IotSvgMetadata, + IotSvgProperty, + IotSvgPropertyType +} from '@home/components/widget/lib/svg/iot-svg.models'; +import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models'; +import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; +import { TbHighlightRule } from '@shared/models/ace/ace.models'; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; import ITooltipsterHelper = JQueryTooltipster.ITooltipsterHelper; import ITooltipPosition = JQueryTooltipster.ITooltipPosition; +import { ValueType } from '@shared/models/constants'; export interface ScadaSymbolData { imageResource: ImageResourceInfo; @@ -747,3 +758,243 @@ export class ScadaSymbolElement { } } + +const scadaSymbolCtxObjectHighlightRules: TbHighlightRule[] = [ + { + class: 'scada-symbol-ctx', + regex: /(?<=\W|^)(ctx)(?=\.)\b/ + } +]; + +export const scadaSymbolGeneralStateRenderHighlightRules: TbHighlightRule[] = + scadaSymbolCtxObjectHighlightRules.concat({ + class: 'scada-symbol-svg', + regex: /(?<=\W|^)(svg)(?=\.)\b/ + }); + +export const scadaSymbolElementStateRenderHighlightRules: TbHighlightRule[] = + scadaSymbolCtxObjectHighlightRules.concat({ + class: 'scada-symbol-element', + regex: /(?<=\W|^)(element)(?=\.)\b/ + }); + +export const scadaSymbolClickActionHighlightRules: TbHighlightRule[] = + scadaSymbolCtxObjectHighlightRules.concat({ + class: 'scada-symbol-event', + regex: /(?<=\W|^)(event)(?=\.)\b/ + }); + +const scadaSymbolCtxPropertyHighlightRules: TbHighlightRule[] = [ + { + class: 'scada-symbol-ctx-properties', + regex: /(?<=ctx\.)(properties)\b/ + }, + { + class: 'scada-symbol-ctx-tags', + regex: /(?<=ctx\.)(tags)\b/ + }, + { + class: 'scada-symbol-ctx-values', + regex: /(?<=ctx\.)(values)\b/ + }, + { + class: 'scada-symbol-ctx-api', + regex: /(?<=ctx\.)(api)\b/ + }, + { + class: 'scada-symbol-ctx-property', + regex: /(?<=ctx\.properties\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }, + { + class: 'scada-symbol-ctx-tag', + regex: /(?<=ctx\.tags\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }, + { + class: 'scada-symbol-ctx-value', + regex: /(?<=ctx\.values\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }, + { + class: 'scada-symbol-ctx-api-method', + regex: /(?<=ctx\.api\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + } +]; + +export const scadaSymbolGeneralStateRenderPropertiesHighlightRules: TbHighlightRule[] = + scadaSymbolCtxPropertyHighlightRules.concat({ + class: 'scada-symbol-svg-properties', + regex: /(?<=svg\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }); + +export const scadaSymbolElementStateRenderPropertiesHighlightRules: TbHighlightRule[] = + scadaSymbolCtxPropertyHighlightRules.concat({ + class: 'scada-symbol-element-properties', + regex: /(?<=element\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }); + +export const scadaSymbolClickActionPropertiesHighlightRules: TbHighlightRule[] = + scadaSymbolCtxPropertyHighlightRules.concat({ + class: 'scada-symbol-event-properties', + regex: /(?<=event\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ + }); + +export const generalStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { + return { + ctx: ctxCompletion, + svg: { + meta: 'argument', + type: 'Svg', + description: 'A root svg node. Instance of SVG.Svg.' + } + }; +}; + +export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { + return { + ctx: ctxCompletion, + element: { + meta: 'argument', + type: 'Element', + description: 'An SVG element.' + }, + }; +}; + +export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { + return { + ctx: ctxCompletion, + event: { + meta: 'argument', + type: 'Event', + description: 'DOM event.' + }, + }; +}; + +export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[], + customTranslate: CustomTranslatePipe): TbEditorCompletion => { + const properties: TbEditorCompletion = { + meta: 'object', + type: 'object', + description: 'An object holding all defined SVG object properties.', + children: {} + }; + for (const property of metadata.properties) { + properties.children[property.id] = iotSvgPropertyCompletion(property, customTranslate); + } + const values: TbEditorCompletion = { + meta: 'object', + type: 'object', + description: 'An object holding all values obtained using behaviors of type \'Value\'', + children: {} + }; + const getValues = metadata.behavior.filter(b => b.type === IotSvgBehaviorType.value); + for (const value of getValues) { + values.children[value.id] = iotSvgValueCompletion(value, customTranslate); + } + const tagsCompletions: TbEditorCompletion = { + meta: 'object', + type: 'object', + description: 'An object holding all tagged SVG elements grouped by tags (object keys).', + children: {} + }; + if (tags?.length) { + for (const tag of tags) { + tagsCompletions.children[tag] = { + meta: 'property', + description: 'An array of SVG elements.', + type: 'Array' + }; + } + } + return { + meta: 'argument', + type: 'IotSvgContext', + description: 'Context of svg object.', + children: { + api: { + meta: 'object', + type: 'IotSvgApi', + description: 'Svg object API', + children: { + animate: { + meta: 'function', + description: 'Animate SVG element', + args: [ + { + name: 'element', + description: 'SVG element', + type: 'Element' + }, + { + name: 'duration', + description: 'Animation duretion is milliseconds', + type: 'number' + } + ], + return: { + description: 'Instance of SVG.Runner which has the same methods as any element and additional methods to control the runner.', + type: 'SVG.Runner' + } + } + } + }, + properties, + values, + tags: tagsCompletions + } + }; +}; + +const iotSvgPropertyCompletion = (property: IotSvgProperty, customTranslate: CustomTranslatePipe): TbEditorCompletion => { + let description = customTranslate.transform(property.name, property.name); + if (property.subLabel) { + description += ` ${customTranslate.transform(property.subLabel, property.subLabel)}`; + } + return { + meta: 'property', + description, + type: iotSvgPropertyCompletionType(property.type) + }; +}; + +const iotSvgValueCompletion = (value: IotSvgBehavior, customTranslate: CustomTranslatePipe): TbEditorCompletion => { + const description = customTranslate.transform(value.name, value.name); + return { + meta: 'property', + description, + type: iotSvgValueCompletionType(value.valueType) + }; +}; + +const iotSvgPropertyCompletionType = (type: IotSvgPropertyType): string => { + switch (type) { + case IotSvgPropertyType.text: + return 'string'; + case IotSvgPropertyType.number: + return 'number'; + case IotSvgPropertyType.switch: + return 'boolean'; + case IotSvgPropertyType.color: + return 'color string'; + case IotSvgPropertyType.color_settings: + return 'ColorProcessor'; + case IotSvgPropertyType.font: + return 'Font'; + case IotSvgPropertyType.units: + return 'units string'; + } +}; + +const iotSvgValueCompletionType = (type: ValueType): string => { + switch (type) { + case ValueType.STRING: + return 'string'; + case ValueType.INTEGER: + case ValueType.DOUBLE: + return 'number'; + case ValueType.BOOLEAN: + return 'boolean'; + case ValueType.JSON: + return 'object'; + } +}; 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 857982c718..8c3fa0a423 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -27,13 +27,13 @@ import { } from '@angular/core'; import { ControlValueAccessor, UntypedFormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; import { Ace } from 'ace-builds'; -import { getAce, Range } from '@shared/models/ace/ace.models'; +import { getAce, Range, TbHighlightRule } from '@shared/models/ace/ace.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UtilsService } from '@core/services/utils.service'; -import { guid, isUndefined } from '@app/core/utils'; +import { deepClone, 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'; @@ -90,6 +90,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @Input() editorCompleter: TbEditorCompleter; + @Input() propertyHighlightRules: TbHighlightRule[]; + + @Input() objectHighlightRules: TbHighlightRule[]; + @Input() globalVariables: Array; @Input() disableUndefinedCheck = false; @@ -216,6 +220,32 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, }); } // @ts-ignore + if ((this.propertyHighlightRules?.length || this.objectHighlightRules?.length) && !!this.jsEditor.session.$mode) { + // @ts-ignore + const newMode = new this.jsEditor.session.$mode.constructor(); + newMode.$highlightRules = new newMode.HighlightRules(); + if (this.propertyHighlightRules?.length) { + const propertiesRules: { token: string; regex: RegExp }[] = newMode.$highlightRules.$rules.property; + const index = propertiesRules.findIndex(p => p.token === 'support.constant'); + const additionalPropertyRules: { token: string; regex: RegExp }[] = this.propertyHighlightRules.map(r => ({ + token: `tb.${r.class}`, + regex: r.regex + })); + propertiesRules.splice(index, 0, ...additionalPropertyRules); + } + if (this.objectHighlightRules?.length) { + const noRegexRules: { token: string; regex: RegExp }[] = newMode.$highlightRules.$rules.no_regex; + const index = noRegexRules.findIndex(p => Array.isArray(p.token) && p.token[0] === 'support.constant'); + const additionalNoRegexRules: { token: string; regex: RegExp }[] = this.objectHighlightRules.map(r => ({ + token: `tb.${r.class}`, + regex: r.regex + })); + noRegexRules.splice(index, 0, ...additionalNoRegexRules); + } + // @ts-ignore + this.jsEditor.session.$onChangeMode(newMode); + } + // @ts-ignore if (!!this.jsEditor.session.$worker) { const jsWorkerOptions = { undef: !this.disableUndefinedCheck, diff --git a/ui-ngx/src/app/shared/models/ace/ace.models.ts b/ui-ngx/src/app/shared/models/ace/ace.models.ts index a63064da43..d21859e531 100644 --- a/ui-ngx/src/app/shared/models/ace/ace.models.ts +++ b/ui-ngx/src/app/shared/models/ace/ace.models.ts @@ -347,3 +347,9 @@ export class Range implements Ace.Range { } } + +export interface TbHighlightRule { + class: string; + regex: RegExp; +} + diff --git a/ui-ngx/src/app/shared/models/ace/completion.models.ts b/ui-ngx/src/app/shared/models/ace/completion.models.ts index 9a8613d6f1..bfba8e5616 100644 --- a/ui-ngx/src/app/shared/models/ace/completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/completion.models.ts @@ -59,6 +59,10 @@ export class TbEditorCompleter implements Ace.Completer { constructor(private editorCompletions: TbEditorCompletions) { } + updateCompletions(completions: TbEditorCompletions): void { + this.editorCompletions = completions; + } + getCompletions(editor: Ace.Editor, session: Ace.EditSession, position: Ace.Point, prefix: string, callback: Ace.CompleterCallback): void { const result = this.prepareCompletions(prefix);