Browse Source

Merge d872e6b61c into d4329f3b7c

pull/15919/merge
Maksym Tsymbarov 2 days ago
committed by GitHub
parent
commit
bc155212d9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 46
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts
  2. 17
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts
  3. 27
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html
  4. 9
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.scss
  5. 81
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts
  6. 6
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html
  7. 9
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.scss
  8. 33
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts
  9. 2
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html
  10. 22
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts
  11. 30
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts
  12. 63
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts
  13. 9
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts
  14. 65
      ui-ngx/src/assets/help/en_US/scada/tag_contextmenu_action_fn.md
  15. 65
      ui-ngx/src/assets/help/en_US/scada/tag_dblclick_action_fn.md
  16. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

46
ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts

@ -116,7 +116,29 @@ export type ScadaSymbolStateRenderFunction = (ctx: ScadaSymbolContext, svg: Svg)
export type ScadaSymbolTagStateRenderFunction = (ctx: ScadaSymbolContext, element: Element) => void;
// noinspection JSUnusedGlobalSymbols
export type ScadaSymbolActionTrigger = 'click';
export type ScadaSymbolActionTrigger = 'click' | 'dblclick' | 'contextmenu';
export const scadaSymbolActionTriggers: ScadaSymbolActionTrigger[] = ['click', 'dblclick', 'contextmenu'];
// Max interval between clicks of a double click: a click action is deferred by this amount
// when the same tag also has a dblclick action, so it can be canceled by the second click.
const dblClickDelay = 300;
export const scadaSymbolActionTriggerTranslations = new Map<ScadaSymbolActionTrigger, string>(
[
['click', 'scada.tag.on-click-action'],
['dblclick', 'scada.tag.on-double-click-action'],
['contextmenu', 'scada.tag.on-right-click-action']
]
);
export const scadaSymbolActionTriggerHelpIds = new Map<ScadaSymbolActionTrigger, string>(
[
['click', 'scada/tag_click_action_fn'],
['dblclick', 'scada/tag_dblclick_action_fn'],
['contextmenu', 'scada/tag_contextmenu_action_fn']
]
);
export type ScadaSymbolActionFunction = (ctx: ScadaSymbolContext, element: Element, event: Event) => void;
export interface ScadaSymbolAction {
@ -669,13 +691,29 @@ export class ScadaSymbolObject {
for (const tag of this.metadata.tags) {
if (tag.actions) {
const elements = this.svgShape.find(`[tb\\:tag="${tag.tag}"]`);
const deferClick = !!tag.actions.click && !!tag.actions.dblclick;
for (const trigger of Object.keys(tag.actions)) {
const action = tag.actions[trigger];
elements.forEach(element => {
element.attr('cursor', 'pointer');
element.on(trigger, (event) => {
action.action(this.context, element, event);
});
if (trigger === 'click' && deferClick) {
let clickTimeout: ReturnType<typeof setTimeout> = null;
element.on('click', (event: MouseEvent) => {
clearTimeout(clickTimeout);
if (event.detail === 1) {
clickTimeout = setTimeout(() => {
action.action(this.context, element, event);
}, dblClickDelay);
}
});
} else {
element.on(trigger, (event) => {
if (trigger === 'contextmenu') {
event.preventDefault();
}
action.action(this.context, element, event);
});
}
});
}
}

17
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts

@ -30,10 +30,15 @@ import { WidgetService } from '@core/http/widget.service';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { AceHighlightRules } from '@shared/models/ace/ace.models';
import {
scadaSymbolClickActionHighlightRules,
scadaSymbolActionFunctionHighlightRules,
scadaSymbolRenderFunctionHighlightRules
} from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { JsFuncComponent } from '@shared/components/js-func.component';
import {
ScadaSymbolActionTrigger,
scadaSymbolActionTriggerHelpIds,
scadaSymbolActionTriggerTranslations
} from '@home/components/widget/lib/scada/scada-symbol.models';
@Component({
selector: 'tb-scada-symbol-metadata-tag-function-panel',
@ -51,7 +56,7 @@ export class ScadaSymbolMetadataTagFunctionPanelComponent implements OnInit, Aft
tagFunction: string;
@Input()
tagFunctionType: 'renderFunction' | 'clickAction';
tagFunctionType: 'renderFunction' | ScadaSymbolActionTrigger;
@Input()
tag: string;
@ -98,11 +103,11 @@ export class ScadaSymbolMetadataTagFunctionPanelComponent implements OnInit, Aft
this.tagFunctionArgs = ['ctx', 'element'];
this.highlightRules = scadaSymbolRenderFunctionHighlightRules;
this.tagFunctionHelpId = 'scada/tag_state_render_fn';
} else if (this.tagFunctionType === 'clickAction') {
this.panelTitle = 'scada.tag.on-click-action';
} else {
this.panelTitle = scadaSymbolActionTriggerTranslations.get(this.tagFunctionType);
this.tagFunctionArgs = ['ctx', 'element', 'event'];
this.highlightRules = scadaSymbolClickActionHighlightRules;
this.tagFunctionHelpId = 'scada/tag_click_action_fn';
this.highlightRules = scadaSymbolActionFunctionHighlightRules;
this.tagFunctionHelpId = scadaSymbolActionTriggerHelpIds.get(this.tagFunctionType);
}
}

27
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html

@ -37,16 +37,19 @@
</button>
}
</div>
<div class="tb-on-click-action-field">
@if (!disabled || tagFormGroup.get('clickAction').value) {
<button
#editClickActionButton
mat-button
color="primary"
(click)="editClickAction()">
<mat-icon>{{ disabled ? 'visibility' : (tagFormGroup.get('clickAction').value ? 'edit' : 'add') }}</mat-icon>
<span>{{ ( disabled ? 'action.view' : (tagFormGroup.get('clickAction').value ? 'action.edit' : 'action.add') ) | translate }}</span>
</button>
}
</div>
@for (trigger of actionTriggers; track trigger) {
<div class="tb-action-function-field">
@if (!disabled || tagFormGroup.get(actionControlName(trigger)).value) {
<button
#editActionButton
[attr.data-trigger]="trigger"
mat-button
color="primary"
(click)="editAction(trigger)">
<mat-icon>{{ disabled ? 'visibility' : (tagFormGroup.get(actionControlName(trigger)).value ? 'edit' : 'add') }}</mat-icon>
<span>{{ ( disabled ? 'action.view' : (tagFormGroup.get(actionControlName(trigger)).value ? 'action.edit' : 'action.add') ) | translate }}</span>
</button>
}
</div>
}
</div>

9
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.scss

@ -15,15 +15,10 @@
*/
.tb-scada-symbol-metadata-tag-row {
.tb-tag-field {
flex: 1 1 70%;
flex: 1 1 40%;
}
.tb-state-render-function-field {
.tb-state-render-function-field, .tb-action-function-field {
flex: 1 1 15%;
}
.tb-on-click-action-field {
flex: 1 1 15%;
}
.tb-state-render-function-field, .tb-on-click-action-field {
display: flex;
justify-content: flex-end;
.mdc-button {

81
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts

@ -19,8 +19,10 @@ import {
forwardRef,
Input,
OnInit,
QueryList,
Renderer2,
ViewChild,
ViewChildren,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
@ -34,7 +36,11 @@ import {
UntypedFormGroup,
Validator
} from '@angular/forms';
import { ScadaSymbolTag } from '@home/components/widget/lib/scada/scada-symbol.models';
import {
ScadaSymbolActionTrigger,
scadaSymbolActionTriggers,
ScadaSymbolTag
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
@ -66,8 +72,8 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
@ViewChild('editStateRenderFunctionButton')
editStateRenderFunctionButton: MatButton;
@ViewChild('editClickActionButton')
editClickActionButton: MatButton;
@ViewChildren('editActionButton')
editActionButtons: QueryList<MatButton>;
@Input()
disabled: boolean;
@ -76,7 +82,9 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
elementStateRenderFunctionCompleter: TbEditorCompleter;
@Input()
clickActionFunctionCompleter: TbEditorCompleter;
actionFunctionCompleter: TbEditorCompleter;
actionTriggers = scadaSymbolActionTriggers;
tagFormGroup: UntypedFormGroup;
@ -91,11 +99,14 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
}
ngOnInit() {
this.tagFormGroup = this.fb.group({
const controls: {[key: string]: any} = {
tag: [null, []],
stateRenderFunction: [null, []],
clickAction: [null, []]
});
stateRenderFunction: [null, []]
};
for (const trigger of this.actionTriggers) {
controls[this.actionControlName(trigger)] = [null, []];
}
this.tagFormGroup = this.fb.group(controls);
}
registerOnChange(fn: any): void {
@ -116,14 +127,14 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
writeValue(value: ScadaSymbolTag): void {
this.modelValue = value;
const clickAction = value?.actions?.click?.actionFunction;
this.tagFormGroup.patchValue(
{
tag: value?.tag,
stateRenderFunction: value?.stateRenderFunction,
clickAction
}, {emitEvent: false}
);
const patch: {[key: string]: any} = {
tag: value?.tag,
stateRenderFunction: value?.stateRenderFunction
};
for (const trigger of this.actionTriggers) {
patch[this.actionControlName(trigger)] = value?.actions?.[trigger]?.actionFunction;
}
this.tagFormGroup.patchValue(patch, {emitEvent: false});
}
public validate(_c: UntypedFormControl) {
@ -135,15 +146,23 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
};
}
actionControlName(trigger: ScadaSymbolActionTrigger): string {
return trigger + 'Action';
}
editTagStateRenderFunction(): void {
this.openTagFunction('renderFunction', this.editStateRenderFunctionButton);
}
editClickAction(): void {
this.openTagFunction('clickAction', this.editClickActionButton);
editAction(trigger: ScadaSymbolActionTrigger): void {
const button = this.editActionButtons
.find(b => b._elementRef.nativeElement.getAttribute('data-trigger') === trigger);
if (button) {
this.openTagFunction(trigger, button);
}
}
private openTagFunction(tagFunctionType: 'renderFunction' | 'clickAction',
private openTagFunction(tagFunctionType: 'renderFunction' | ScadaSymbolActionTrigger,
button: MatButton) {
const trigger = button._elementRef.nativeElement;
trigger.scrollIntoView();
@ -155,9 +174,9 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
if (tagFunctionType === 'renderFunction') {
tagFunctionControl = this.tagFormGroup.get('stateRenderFunction');
completer = this.elementStateRenderFunctionCompleter;
} else if (tagFunctionType === 'clickAction') {
tagFunctionControl = this.tagFormGroup.get('clickAction');
completer = this.clickActionFunctionCompleter;
} else {
tagFunctionControl = this.tagFormGroup.get(this.actionControlName(tagFunctionType));
completer = this.actionFunctionCompleter;
}
const scadaSymbolTagFunctionPanelPopover = this.popoverService.displayPopover({
trigger,
@ -189,15 +208,17 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
tag: value.tag,
stateRenderFunction: value.stateRenderFunction
};
if (value.clickAction) {
this.modelValue.actions = {
click: {
actionFunction: value.clickAction
}
};
} else {
this.modelValue.actions = null;
let actions: ScadaSymbolTag['actions'] = null;
for (const trigger of this.actionTriggers) {
const actionFunction = value[this.actionControlName(trigger)];
if (actionFunction) {
actions = actions || {};
actions[trigger] = {
actionFunction
};
}
}
this.modelValue.actions = actions;
this.propagateChange(this.modelValue);
}
}

6
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html

@ -20,7 +20,9 @@
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell tb-tag-header" translate>scada.tag.tag</div>
<div class="tb-form-table-header-cell tb-state-render-function-header" translate>scada.state-render-function</div>
<div class="tb-form-table-header-cell tb-on-click-action-header" translate>scada.tag.on-click-action</div>
@for (trigger of actionTriggers; track trigger) {
<div class="tb-form-table-header-cell tb-action-function-header">{{ actionTriggerTranslations.get(trigger) | translate }}</div>
}
</div>
@if (tagsFormArray().controls.length) {
<div class="tb-form-table-body">
@ -28,7 +30,7 @@
<div>
<tb-scada-symbol-metadata-tag class="flex-1"
[elementStateRenderFunctionCompleter]="elementStateRenderFunctionCompleter"
[clickActionFunctionCompleter]="clickActionFunctionCompleter"
[actionFunctionCompleter]="actionFunctionCompleter"
[formControl]="tagControl">
</tb-scada-symbol-metadata-tag>
@if (!$last) {

9
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.scss

@ -18,15 +18,10 @@
margin: 12px;
.tb-form-table-header-cell {
&.tb-tag-header {
flex: 1 1 70%;
flex: 1 1 40%;
}
&.tb-state-render-function-header {
&.tb-state-render-function-header, &.tb-action-function-header {
flex: 1 1 15%;
}
&.tb-on-click-action-header {
flex: 1 1 15%;
}
&.tb-state-render-function-header, &.tb-on-click-action-header {
display: flex;
justify-content: flex-end;
}

33
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts

@ -38,7 +38,12 @@ import {
UntypedFormGroup,
Validator
} from '@angular/forms';
import { ScadaSymbolTag } from '@home/components/widget/lib/scada/scada-symbol.models';
import {
ScadaSymbolActionTrigger,
scadaSymbolActionTriggers,
scadaSymbolActionTriggerTranslations,
ScadaSymbolTag
} from '@home/components/widget/lib/scada/scada-symbol.models';
import {
ScadaSymbolMetadataTagComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
@ -46,7 +51,7 @@ import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
const tagIsEmpty = (tag: ScadaSymbolTag): boolean =>
!tag.stateRenderFunction && !tag.actions?.click?.actionFunction;
!tag.stateRenderFunction && !scadaSymbolActionTriggers.some(trigger => tag.actions?.[trigger]?.actionFunction);
@Component({
selector: 'tb-scada-symbol-metadata-tags',
@ -85,7 +90,11 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
elementStateRenderFunctionCompleter: TbEditorCompleter;
@Input()
clickActionFunctionCompleter: TbEditorCompleter;
actionFunctionCompleter: TbEditorCompleter;
actionTriggers = scadaSymbolActionTriggers;
actionTriggerTranslations = scadaSymbolActionTriggerTranslations;
tagsFormGroup: UntypedFormGroup;
@ -184,12 +193,12 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
});
}
editTagClickAction(tag: string): void {
editTagAction(tag: string, trigger: ScadaSymbolActionTrigger): void {
setTimeout(() => {
const tags: ScadaSymbolTag[] = this.tagsFormGroup.get('tags').value;
const index = tags.findIndex(t => t.tag === tag);
const tagComponent = this.metadataTags.get(index);
tagComponent?.editClickAction();
tagComponent?.editAction(trigger);
});
}
@ -204,12 +213,14 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
const found = result.find(t => t.tag === tag.tag);
if (found) {
found.stateRenderFunction = tag.stateRenderFunction;
if (tag.actions?.click?.actionFunction) {
found.actions = {
click: {
actionFunction: tag.actions.click.actionFunction
}
};
for (const trigger of scadaSymbolActionTriggers) {
const actionFunction = tag.actions?.[trigger]?.actionFunction;
if (actionFunction) {
found.actions = found.actions || {};
found.actions[trigger] = {
actionFunction
};
}
}
}
}

2
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html

@ -95,7 +95,7 @@
<tb-scada-symbol-metadata-tags
#symbolMetadataTags
[tags]="tags"
[clickActionFunctionCompleter]="clickActionFunctionCompleter"
[actionFunctionCompleter]="actionFunctionCompleter"
[elementStateRenderFunctionCompleter]="elementStateRenderFunctionCompleter"
formControlName="tags">
</tb-scada-symbol-metadata-tags>

22
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts

@ -36,7 +36,11 @@ import {
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { emptyMetadata, ScadaSymbolMetadata } from '@home/components/widget/lib/scada/scada-symbol.models';
import {
emptyMetadata,
ScadaSymbolActionTrigger,
ScadaSymbolMetadata
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
@ -46,7 +50,7 @@ import {
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import {
clickActionFunctionCompletions,
actionFunctionCompletions,
elementStateRenderFunctionCompletions,
generalStateRenderFunctionCompletions,
scadaSymbolContextCompletion,
@ -122,7 +126,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
generalStateRenderFunctionCompleter: TbEditorCompleter;
elementStateRenderFunctionCompleter: TbEditorCompleter;
clickActionFunctionCompleter: TbEditorCompleter;
actionFunctionCompleter: TbEditorCompleter;
highlightRules = scadaSymbolGeneralStateHighlightRules;
@ -205,9 +209,9 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
this.symbolMetadataTags.editTagStateRenderFunction(tag);
}
editTagClickAction(tag: string): void {
editTagAction(tag: string, trigger: ScadaSymbolActionTrigger): void {
this.selectedOption = 'tags';
this.symbolMetadataTags.editTagClickAction(tag);
this.symbolMetadataTags.editTagAction(tag, trigger);
}
public validate(c: UntypedFormControl) {
@ -240,11 +244,11 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
} else {
this.elementStateRenderFunctionCompleter.updateCompletions(elementStateRender);
}
const clickAction = clickActionFunctionCompletions(contextCompleter);
if (!this.clickActionFunctionCompleter) {
this.clickActionFunctionCompleter = new TbEditorCompleter(clickAction);
const actionFunction = actionFunctionCompletions(contextCompleter);
if (!this.actionFunctionCompleter) {
this.actionFunctionCompleter = new TbEditorCompleter(actionFunction);
} else {
this.clickActionFunctionCompleter.updateCompletions(clickAction);
this.actionFunctionCompleter.updateCompletions(actionFunction);
}
}
}

30
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts

@ -24,6 +24,8 @@ import {
setupTagPanelTooltip
} from '@home/pages/scada-symbol/scada-symbol-tooltip.components';
import {
ScadaSymbolActionTrigger,
scadaSymbolActionTriggers,
ScadaSymbolBehavior,
ScadaSymbolBehaviorType,
scadaSymbolContentData,
@ -51,9 +53,9 @@ export interface ScadaSymbolData {
export interface ScadaSymbolEditObjectCallbacks {
tagHasStateRenderFunction: (tag: string) => boolean;
tagHasClickAction: (tag: string) => boolean;
tagHasAction: (tag: string, trigger: ScadaSymbolActionTrigger) => boolean;
editTagStateRenderFunction: (tag: string) => void;
editTagClickAction: (tag: string) => void;
editTagAction: (tag: string, trigger: ScadaSymbolActionTrigger) => void;
tagsUpdated: (tags: string[]) => void;
hasHiddenElements?: (hasHidden: boolean) => void;
onSymbolEditObjectDirty: (dirty: boolean) => void;
@ -423,16 +425,16 @@ export class ScadaSymbolEditObject {
return this.callbacks.tagHasStateRenderFunction(tag);
}
public tagHasClickAction(tag: string): boolean {
return this.callbacks.tagHasClickAction(tag);
public tagHasAction(tag: string, trigger: ScadaSymbolActionTrigger): boolean {
return this.callbacks.tagHasAction(tag, trigger);
}
public editTagStateRenderFunction(tag: string) {
this.callbacks.editTagStateRenderFunction(tag);
}
public editTagClickAction(tag: string) {
this.callbacks.editTagClickAction(tag);
public editTagAction(tag: string, trigger: ScadaSymbolActionTrigger) {
this.callbacks.editTagAction(tag, trigger);
}
public setDirty(dirty: boolean) {
@ -645,23 +647,27 @@ export class ScadaSymbolElement {
}
}
public hasClickAction(): boolean {
public hasAction(trigger: ScadaSymbolActionTrigger): boolean {
if (this.hasTag()) {
return this.editObject.tagHasClickAction(this.tag);
return this.editObject.tagHasAction(this.tag, trigger);
} else {
return false;
}
}
public hasAnyAction(): boolean {
return scadaSymbolActionTriggers.some(trigger => this.hasAction(trigger));
}
public editStateRenderFunction() {
if (this.hasTag()) {
this.editObject.editTagStateRenderFunction(this.tag);
}
}
public editClickAction() {
public editAction(trigger: ScadaSymbolActionTrigger) {
if (this.hasTag()) {
this.editObject.editTagClickAction(this.tag);
this.editObject.editTagAction(this.tag, trigger);
}
}
@ -1081,7 +1087,7 @@ export const scadaSymbolRenderFunctionHighlightRules: AceHighlightRules = {
...scadaSymbolElementPropertyHighlightRules
};
export const scadaSymbolClickActionHighlightRules: AceHighlightRules = {
export const scadaSymbolActionFunctionHighlightRules: AceHighlightRules = {
start: [
scadaSymbolCtxObjectHighlightRule,
scadaSymbolElementHighlightRule,
@ -1114,7 +1120,7 @@ export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCom
}
});
export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => {
export const actionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => {
const completions = elementStateRenderFunctionCompletions(ctxCompletion);
completions.event = {
meta: 'argument',

63
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts

@ -31,6 +31,11 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ScadaSymbolElement } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import {
ScadaSymbolActionTrigger,
scadaSymbolActionTriggers,
scadaSymbolActionTriggerTranslations
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { ENTER } from '@angular/cdk/keycodes';
@ -241,7 +246,7 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
ngOnInit() {
if (this.symbolElement?.readonly) {
this.displayTagSettings = (this.symbolElement?.hasStateRenderFunction() || this.symbolElement?.hasClickAction());
this.displayTagSettings = (this.symbolElement?.hasStateRenderFunction() || this.symbolElement?.hasAnyAction());
}
}
@ -387,26 +392,28 @@ class ScadaSymbolRemoveTagConfirmComponent extends ScadaSymbolPanelComponent imp
<span translate>action.add</span>
</button>
}
@if (!symbolElement?.readonly || hasClickAction) {
<div translate>scada.tag.on-click-action</div>
}
@if (hasClickAction) {
<button
mat-stroked-button
color="primary"
(click)="editClickAction()">
<mat-icon>{{ symbolElement?.readonly ? 'visibility' : 'edit' }}</mat-icon>
<span>{{ (symbolElement?.readonly ? 'action.view' : 'action.edit') | translate }}</span>
</button>
}
@if (!hasClickAction && !symbolElement?.readonly) {
<button
mat-stroked-button
color="primary"
(click)="editClickAction()">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
@for (trigger of actionTriggers; track trigger) {
@if (!symbolElement?.readonly || hasActions[trigger]) {
<div>{{ actionTriggerTranslations.get(trigger) | translate }}</div>
}
@if (hasActions[trigger]) {
<button
mat-stroked-button
color="primary"
(click)="editAction(trigger)">
<mat-icon>{{ symbolElement?.readonly ? 'visibility' : 'edit' }}</mat-icon>
<span>{{ (symbolElement?.readonly ? 'action.view' : 'action.edit') | translate }}</span>
</button>
}
@if (!hasActions[trigger] && !symbolElement?.readonly) {
<button
mat-stroked-button
color="primary"
(click)="editAction(trigger)">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
}
}
</div>`,
styleUrls: ['./scada-symbol-tooltip.component.scss'],
@ -415,9 +422,13 @@ class ScadaSymbolRemoveTagConfirmComponent extends ScadaSymbolPanelComponent imp
})
class ScadaSymbolTagSettingsComponent extends ScadaSymbolPanelComponent implements OnInit, AfterViewInit {
actionTriggers = scadaSymbolActionTriggers;
actionTriggerTranslations = scadaSymbolActionTriggerTranslations;
hasStateRenderFunction = false;
hasClickAction = false;
hasActions: {[trigger in ScadaSymbolActionTrigger]?: boolean} = {};
constructor(public element: ElementRef<HTMLElement>) {
super(element);
@ -429,15 +440,17 @@ class ScadaSymbolTagSettingsComponent extends ScadaSymbolPanelComponent implemen
updateFunctionsState() {
this.hasStateRenderFunction = this.symbolElement.hasStateRenderFunction();
this.hasClickAction = this.symbolElement.hasClickAction();
for (const trigger of this.actionTriggers) {
this.hasActions[trigger] = this.symbolElement.hasAction(trigger);
}
}
editStateRenderFunction() {
this.symbolElement.editStateRenderFunction();
}
editClickAction() {
this.symbolElement.editClickAction();
editAction(trigger: ScadaSymbolActionTrigger) {
this.symbolElement.editAction(trigger);
}
}

9
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts

@ -33,6 +33,7 @@ import { Subject } from 'rxjs';
import { ScadaSymbolData, ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import {
parseScadaSymbolMetadataFromContent,
ScadaSymbolActionTrigger,
ScadaSymbolMetadata,
ScadaSymbolObjectSettings,
updateScadaSymbolMetadataInContent
@ -330,11 +331,11 @@ export class ScadaSymbolComponent extends PageComponent
return false;
}
tagHasClickAction(tag: string): boolean {
tagHasAction(tag: string, trigger: ScadaSymbolActionTrigger): boolean {
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.tags) {
const found = metadata.tags.find(t => t.tag === tag);
return !!found?.actions?.click?.actionFunction;
return !!found?.actions?.[trigger]?.actionFunction;
}
return false;
}
@ -343,8 +344,8 @@ export class ScadaSymbolComponent extends PageComponent
this.symbolMetadata.editTagStateRenderFunction(tag);
}
editTagClickAction(tag: string) {
this.symbolMetadata.editTagClickAction(tag);
editTagAction(tag: string, trigger: ScadaSymbolActionTrigger) {
this.symbolMetadata.editTagAction(tag, trigger);
}
onSymbolEditObjectDirty(dirty: boolean) {

65
ui-ngx/src/assets/help/en_US/scada/tag_contextmenu_action_fn.md

@ -0,0 +1,65 @@
#### Symbol tag right click action function
<div class="divider"></div>
<br/>
*function (ctx, element, event): void*
A JavaScript function invoked when user right-clicks on SVG element with specific tag.
**Parameters:**
<ul>
<li><b>ctx:</b> <code>ScadaSymbolContext</code> - <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolcontext" target="_blank">Context</a> of the SCADA symbol.
</li>
<li><b>element:</b> <code>Element</code> - <a href="https://svgjs.dev/docs/3.2/getting-started/" target="_blank">SVG.js</a> element.<br>
See the examples below to learn how to <a href="https://svgjs.dev/docs/3.2/manipulating/" target="_blank">manipulate</a> and <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolanimation" target="_blank">animate</a> elements.<br>
</li>
<li><b>event:</b> <code>Event</code> - DOM event.
</li>
</ul>
<div class="divider"></div>
The browser context menu is automatically suppressed on the element, so the configured action is executed instead.
Note that on touch devices the right click action is typically triggered by a long press.
##### Example
**Invoke widget right click action**
<br>
This JavaScript snippet demonstrates triggering a <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/actions/#action-types" target="_blank">widget action</a> using the <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolcontext" target="_blank">ScadaSymbolContext API</a> when the right click event occurs. The widget action will be linked to the behaviorId 'rightClick', which defines the action that will be executed upon the event.
The behavior of this action depends on the type of widget action configured in the ThingsBoard platform (e.g., navigating to a dashboard state, updating the current state, opening a URL, etc.).
<br>
```javascript
ctx.api.callAction(event, 'rightClick'); // Trigger widget action 'rightClick' on event
{:copy-code}
```
<br>
*Example Use Case*
- **Open details popup:** If the 'rightClick' action is configured to open a popup with device details, right-clicking the element shows contextual information, while the left click keeps its primary action.
- **Custom context menu:** A custom-action widget action can render your own context menu for the element in place of the browser one.
<br>
You can also execute an action behavior directly and pass a value, for example:
```javascript
var running = ctx.values.running; // Current running status from context
// Request device shutdown with observer callbacks for error handling
ctx.api.callAction(event, 'shutdown', undefined, {
error: () => {
// Action failed; keep the current state
ctx.api.setValue('running', running);
}
});
{:copy-code}
```

65
ui-ngx/src/assets/help/en_US/scada/tag_dblclick_action_fn.md

@ -0,0 +1,65 @@
#### Symbol tag double click action function
<div class="divider"></div>
<br/>
*function (ctx, element, event): void*
A JavaScript function invoked when user double-clicks on SVG element with specific tag.
**Parameters:**
<ul>
<li><b>ctx:</b> <code>ScadaSymbolContext</code> - <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolcontext" target="_blank">Context</a> of the SCADA symbol.
</li>
<li><b>element:</b> <code>Element</code> - <a href="https://svgjs.dev/docs/3.2/getting-started/" target="_blank">SVG.js</a> element.<br>
See the examples below to learn how to <a href="https://svgjs.dev/docs/3.2/manipulating/" target="_blank">manipulate</a> and <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolanimation" target="_blank">animate</a> elements.<br>
</li>
<li><b>event:</b> <code>Event</code> - DOM event.
</li>
</ul>
<div class="divider"></div>
If the same tag also has an *on click* action configured, the click action is automatically deferred for a short interval
and cancelled when a double click is detected — only the double click action is executed.
##### Example
**Invoke widget double click action**
<br>
This JavaScript snippet demonstrates triggering a <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/actions/#action-types" target="_blank">widget action</a> using the <a href="${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolcontext" target="_blank">ScadaSymbolContext API</a> when the double click event occurs. The widget action will be linked to the behaviorId 'doubleClick', which defines the action that will be executed upon the event.
The behavior of this action depends on the type of widget action configured in the ThingsBoard platform (e.g., navigating to a dashboard state, updating the current state, opening a URL, etc.).
<br>
```javascript
ctx.api.callAction(event, 'doubleClick'); // Trigger widget action 'doubleClick' on event
{:copy-code}
```
<br>
*Example Use Case*
- **Navigate to Dashboard State:** If the 'doubleClick' action is configured to navigate to a detailed dashboard state, the user will be redirected to that state when double-clicking on the element, while a single click may be reserved for a quick toggle action.
- **Open URL:** If configured to open a URL, double-clicking on the element will take the user to a specified web resource.
<br>
You can also execute an action behavior directly and pass a value, for example:
```javascript
var running = ctx.values.running; // Current running status from context
// Toggle the device state with observer callbacks for next and error handling
ctx.api.callAction(event, 'switchState', !running, {
error: () => {
// Action failed; keep the current state
ctx.api.setValue('running', running);
}
});
{:copy-code}
```

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

@ -3888,6 +3888,8 @@
"tag": {
"tag": "Tag",
"on-click-action": "On click action",
"on-double-click-action": "On double click action",
"on-right-click-action": "On right click action",
"no-tags": "No tags configured",
"delete-tag-text": "Are you sure you want to delete tag<br/><b>{{tag}}</b> from <b>{{elementType}}</b> element?",
"update-tag": "Update tag",

Loading…
Cancel
Save