Browse Source

UI: SCADA editor improvements: handle hidden elements.

pull/11391/head
Igor Kulikov 2 years ago
parent
commit
d524056285
  1. 55
      application/src/main/data/json/system/scada_symbols/horizontal-connector.svg
  2. 4
      application/src/main/data/json/system/scada_symbols/level_and_fan.svg
  3. 4
      application/src/main/data/json/system/widget_types/toggle_button.json
  4. 1
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss
  5. 21
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts
  6. 1
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss
  7. 178
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts
  8. 4
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

55
application/src/main/data/json/system/scada_symbols/horizontal-connector.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 991 B

After

Width:  |  Height:  |  Size: 46 KiB

4
application/src/main/data/json/system/scada_symbols/level_and_fan.svg

@ -12,7 +12,7 @@
"tags": [
{
"tag": "fan",
"stateRenderFunction": "var on = ctx.values.on;\nif (on) {\n var level = ctx.values.level; \n var minLevel = ctx.properties.minLevel; \n var maxLevel = ctx.properties.maxLevel;\n\n var speed = (level - minLevel) / (maxLevel - minLevel);\n speed = Math.max(0, Math.min(1, speed))*2; \n\n var t = element.timeline();\n if (!t.active()) {\n ctx.api.animate(element, 1000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n t.speed(speed);\n} else {\n element.timeline().stop();\n}\n",
"stateRenderFunction": "var on = ctx.values.on;\nvar hasAnimation = element.remember('hasAnimation');\nif (on) {\n\n var level = ctx.values.level; \n var minLevel = ctx.properties.minLevel; \n var maxLevel = ctx.properties.maxLevel;\n\n var speed = (level - minLevel) / (maxLevel - minLevel);\n speed = Math.max(0, Math.min(1, speed))*2; \n\n if (!hasAnimation) {\n element.remember('hasAnimation', true);\n element.animate(1000).ease('-').rotate(360).loop();\n } else {\n element.timeline().play();\n }\n element.timeline().speed(speed);\n} else {\n if (hasAnimation) {\n element.timeline().pause();\n }\n}\n",
"actions": null
},
{
@ -68,7 +68,7 @@
},
{
"tag": "ramka",
"stateRenderFunction": "var on = ctx.values.on;\nif (on) {\n if (!element.timeline().active()) {\n ctx.api.animate(element, 1000).scale(1.1).loop(0, true);\n }\n} else {\n element.timeline().stop();\n}",
"stateRenderFunction": "var on = ctx.values.on;\nvar hasAnimation = element.remember('hasAnimation');\nif (on) {\n if (!hasAnimation) {\n element.remember('hasAnimation', true);\n ctx.api.animate(element, 1000).scale(1.1).loop(0, true);\n } else {\n element.timeline().play();\n }\n} else {\n if (hasAnimation) {\n element.timeline().pause();\n }\n}",
"actions": null
}
],

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

4
application/src/main/data/json/system/widget_types/toggle_button.json

File diff suppressed because one or more lines are too long

1
ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss

@ -45,7 +45,6 @@
display: flex;
align-items: center;
justify-content: center;
fill: none;
}
}
}

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

@ -48,6 +48,8 @@ export interface ScadaSymbolApi {
text: (element: Element | Element[], text: string) => void;
font: (element: Element | Element[], font: Font, color: string) => void;
animate: (element: Element, duration: number) => Runner;
resetAnimation: (element: Element) => void;
finishAnimation: (element: Element) => void;
disable: (element: Element | Element[]) => void;
enable: (element: Element | Element[]) => void;
callAction: (event: Event, behaviorId: string, value?: any, observer?: Partial<Observer<void>>) => void;
@ -480,11 +482,15 @@ export class ScadaSymbolObject {
elements.item(i).remove();
}
this.svgShape = SVG().svg(doc.documentElement.innerHTML);
this.svgShape.node.style.overflow = 'visible';
this.svgShape.node.style.overflow = 'hidden';
this.svgShape.node.style.position = 'absolute';
this.svgShape.node.style['user-select'] = 'none';
const origSvg = SVG(doc.documentElement.outerHTML);
if (origSvg.type === 'svg') {
this.box = (origSvg as Svg).viewbox();
if (origSvg.fill()) {
this.svgShape.fill(origSvg.fill());
}
} else {
this.box = this.svgShape.bbox();
}
@ -500,6 +506,8 @@ export class ScadaSymbolObject {
text: this.setElementText.bind(this),
font: this.setElementFont.bind(this),
animate: this.animate.bind(this),
resetAnimation: this.resetAnimation.bind(this),
finishAnimation: this.finishAnimation.bind(this),
disable: this.disableElement.bind(this),
enable: this.enableElement.bind(this),
callAction: this.callAction.bind(this),
@ -767,9 +775,18 @@ export class ScadaSymbolObject {
}
private animate(element: Element, duration: number): Runner {
this.finishAnimation(element);
return element.animate(duration, 0, 'now');
}
private resetAnimation(element: Element) {
element.timeline().stop();
element.timeline(new Timeline());
}
private finishAnimation(element: Element) {
element.timeline().finish();
element.timeline(new Timeline());
return element.animate(duration, 0, 'now');
}
private disableElement(e: Element | Element[]) {

1
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss

@ -19,7 +19,6 @@
display: flex;
align-items: center;
justify-content: center;
fill: none;
}
.tb-scada-symbol-editor-tooltips {
position: absolute;

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

@ -100,9 +100,12 @@ export class ScadaSymbolEditObject {
this.svgShape = SVG().svg(contentData.innerSvg);
this.svgShape.node.style.overflow = 'visible';
this.svgShape.node.style['user-select'] = 'none';
const origSvg = SVG(svgContent);
const origSvg = SVG(`${contentData.svgRootNode}\n${contentData.innerSvg}\n</svg>`);
if (origSvg.type === 'svg') {
this.box = (origSvg as Svg).viewbox();
if (origSvg.fill()) {
this.svgShape.fill(origSvg.fill());
}
} else {
this.box = this.svgShape.bbox();
}
@ -415,7 +418,12 @@ export class ScadaSymbolEditObject {
const hasBBox = (e: Element): boolean => {
try {
if (e.bbox) {
const box = e.bbox();
let box = e.bbox();
if (!box.width && !box.height && !e.visible()) {
e.show();
box = e.bbox();
e.hide();
}
return !!box.width || !!box.height;
} else {
return false;
@ -425,13 +433,6 @@ const hasBBox = (e: Element): boolean => {
}
};
/*const isDomRectContained = (target: DOMRect, container: DOMRect, horizontalGap = 0, verticalGap = 0): boolean => (
target.left >= container.left - horizontalGap &&
target.left + target.width <= container.left + container.width + horizontalGap &&
target.top >= container.top - verticalGap &&
target.top + target.height <= container.top + container.height + verticalGap
);*/
const elementTooltipMinHeight = 36 + 8;
const elementTooltipMinWidth = 100;
@ -443,6 +444,8 @@ export class ScadaSymbolElement {
private highlightRect: Rect;
private highlightRectTimeline: Timeline;
private elementPlaceholder: Rect;
public tooltip: ITooltipsterInstance;
public tag: string;
@ -456,6 +459,8 @@ export class ScadaSymbolElement {
private highlighted = false;
public invisible = false;
private tooltipMouseX: number;
private tooltipMouseY: number;
@ -463,13 +468,37 @@ export class ScadaSymbolElement {
return this.editObject.readonly;
}
get activeElement(): Element {
return this.invisible ? this.elementPlaceholder : this.element;
}
constructor(private editObject: ScadaSymbolEditObject,
public element: Element) {
this.tag = element.attr('tb:tag');
this.box = element.rbox(this.editObject.svgShape);
if (element.visible()) {
this.box = element.rbox(this.editObject.svgShape);
} else {
element.show();
this.box = element.rbox(this.editObject.svgShape);
element.hide();
this.invisible = true;
}
}
public init() {
if (this.invisible) {
this.elementPlaceholder = this.editObject.svgShape
.rect(this.box.width, this.box.height)
.x(this.box.x)
.y(this.box.y)
.attr({
'tb:inner': true,
fill: 'none',
stroke: 'rgba(0, 0, 0, 0.58)',
'stroke-width': this.unscaled(4),
opacity: 1});
this.element.after(this.elementPlaceholder);
}
if (this.isGroup()) {
this.highlightRect =
this.editObject.svgShape
@ -488,12 +517,12 @@ export class ScadaSymbolElement {
this.highlightRect.timeline(this.highlightRectTimeline);
this.highlightRect.hide();
} else {
this.element.addClass('tb-element');
this.activeElement.addClass('tb-element');
}
this.element.on('mouseenter', (_event) => {
this.activeElement.on('mouseenter', (_event) => {
this.highlight();
});
this.element.on('mouseleave', (_event) => {
this.activeElement.on('mouseleave', (_event) => {
this.unhighlight();
});
if (this.hasTag()) {
@ -524,7 +553,7 @@ export class ScadaSymbolElement {
this.highlightRect.show();
this.highlightRect.animate(300).attr({opacity: 1});
} else {
this.element.addClass('hovered');
this.activeElement.addClass('hovered');
}
if (this.hasTag()) {
this.tooltip.reposition();
@ -542,7 +571,7 @@ export class ScadaSymbolElement {
this.highlightRect.hide();
});
} else {
this.element.removeClass('hovered');
this.activeElement.removeClass('hovered');
}
if (this.hasTag() && !this.editing) {
$(this.tooltip.elementTooltip()).removeClass('tb-active');
@ -668,7 +697,7 @@ export class ScadaSymbolElement {
}
private createTagTooltip() {
const el = $(this.element.node);
const el = $(this.activeElement.node);
el.tooltipster(
{
parent: this.tooltipContainer,
@ -706,7 +735,7 @@ export class ScadaSymbolElement {
}
private createAddTagTooltip() {
const el = $(this.element.node);
const el = $(this.activeElement.node);
el.tooltipster(
{
parent: this.tooltipContainer,
@ -930,8 +959,8 @@ export const generalStateRenderFunctionCompletions = (ctxCompletion: TbEditorCom
ctx: ctxCompletion,
svg: {
meta: 'argument',
type: 'Svg',
description: 'A root svg node. Instance of SVG.Svg.'
type: '<a href="https://svgjs.dev/docs/3.2/container-elements/#svg-svg">SVG.Svg</a>',
description: 'A root svg node. Instance of <a href="https://svgjs.dev/docs/3.2/container-elements/#svg-svg">SVG.Svg</a>.'
}
});
@ -940,8 +969,10 @@ export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCom
element: {
meta: 'argument',
type: 'Element',
description: 'An SVG element.'
},
description: 'SVG element.<br>' +
'See <a href="https://svgjs.dev/docs/3.2/manipulating/">Manipulating</a> section to manipulate the element.<br>' +
'See <a href="https://svgjs.dev/docs/3.2/animating/">Animating</a> section to animate the element.'
}
});
export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => {
@ -959,7 +990,7 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags
const properties: TbEditorCompletion = {
meta: 'object',
type: 'object',
description: 'An object holding all defined SVG object properties.',
description: 'An object holding all defined SCADA symbol properties.',
children: {}
};
for (const property of metadata.properties) {
@ -968,7 +999,7 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags
const values: TbEditorCompletion = {
meta: 'object',
type: 'object',
description: 'An object holding all values obtained using behaviors of type \'Value\'',
description: 'An object holding all values obtained using behaviors of type <b>"Value"</b>',
children: {}
};
const getValues = metadata.behavior.filter(b => b.type === ScadaSymbolBehaviorType.value);
@ -993,16 +1024,16 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags
return {
meta: 'argument',
type: 'ScadaSymbolContext',
description: 'Context of svg object.',
description: 'Context of the SCADA symbol.',
children: {
api: {
meta: 'object',
type: 'ScadaSymbolApi',
description: 'Svg object API',
description: 'SCADA symbol API',
children: {
animate: {
meta: 'function',
description: 'Animate SVG element',
description: 'Finishes any previous animation and starts new animation for SVG element.',
args: [
{
name: 'element',
@ -1017,8 +1048,101 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags
],
return: {
description: 'Instance of SVG.Runner which has the same methods as any element and additional methods to control the runner.',
type: 'SVG.Runner'
type: '<a href="https://svgjs.dev/docs/3.2/animating/#svg-runner">SVG.Runner</a>'
}
},
resetAnimation: {
meta: 'function',
description: 'Stops animation if any and restore SVG element initial state, resets animation timeline.',
args: [
{
name: 'element',
description: 'SVG element',
type: 'Element'
},
]
},
finishAnimation: {
meta: 'function',
description: 'Finishes animation if any, SVG element state updated according to the end animation values, ' +
'resets animation timeline.',
args: [
{
name: 'element',
description: 'SVG element',
type: 'Element'
},
]
},
formatValue: {
meta: 'function',
description: 'Formats numeric value according to specified decimals and units',
args: [
{
name: 'value',
description: 'Numeric value to be formatted',
type: 'any'
},
{
name: 'dec',
description: 'Number of decimal digits',
type: 'number',
optional: true
},
{
name: 'units',
description: 'Units to append to the formatted value',
type: 'string',
optional: true
},
{
name: 'showZeroDecimals',
description: 'Whether to keep zero decimal digits',
type: 'boolean',
optional: true
}
],
return: {
type: 'string',
description: 'Formatted value'
}
},
text: {
meta: 'function',
description: 'Set text to element(s). Only applicable for elements of type ' +
'<a href="https://svgjs.dev/docs/3.2/shape-elements/#svg-text">SVG.Text</a> or ' +
'<a href="https://svgjs.dev/docs/3.2/shape-elements/#svg-tspan">SVG.Tspan</a>.',
args: []
},
font: {
meta: 'function',
description: 'Set element(s) text font and color. Only applicable for elements of type ' +
'<a href="https://svgjs.dev/docs/3.2/shape-elements/#svg-text">SVG.Text</a> or ' +
'<a href="https://svgjs.dev/docs/3.2/shape-elements/#svg-tspan">SVG.Tspan</a>.',
args: []
},
disable: {
meta: 'function',
description: 'Disables element(s). Disabled element doesn\'t accept any user interaction. ' +
'For ex. if disabled element has click action, no click action will be performed on user click.',
args: []
},
enable: {
meta: 'function',
description: 'Enables disabled element(s). Enabled element accepts user interaction. ' +
'For ex. if element has click action, click action will be performed on user click.',
args: []
},
callAction: {
meta: 'function',
description: 'Invokes action specified by behavior of type <b>"Action"</b> found by <b>behaviorId</b>.',
args: []
},
setValue: {
meta: 'function',
description: 'Updates value by <b>valueId</b>. See <code>ctx.values</code> for reference. ' +
'Value update triggers all render functions.',
args: []
}
}
},

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

@ -58,7 +58,7 @@ abstract class ScadaSymbolPanelComponent implements AfterViewInit {
@Component({
template: `<div class="tb-scada-symbol-tooltip-panel">
<span>{{ symbolElement?.element?.type }}:</span>
<span>{{ symbolElement?.element?.type }}{{ symbolElement?.invisible ? ' (' + ('scada.hidden' | translate) + ')' : '' }}</span>
<button mat-stroked-button color="primary" (click)="onAddTag()">
<mat-icon>add</mat-icon>
<span translate>scada.tag.add-tag</span>
@ -182,7 +182,7 @@ class ScadaSymbolTagInputPanelComponent extends ScadaSymbolPanelComponent implem
@Component({
template: `<div class="tb-scada-symbol-tooltip-panel">
<span>{{ symbolElement?.element?.type }}:</span>
<span>{{ symbolElement?.element?.type }}{{ symbolElement?.invisible ? ' (' + ('scada.hidden' | translate) + ')' : '' }}:</span>
<span><b>{{ symbolElement?.tag }}</b></span>
<button *ngIf="!symbolElement?.readonly" type="button" mat-icon-button class="tb-mat-20"
matTooltip="{{ 'scada.tag.update-tag' | translate }}"

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

@ -3480,6 +3480,7 @@
"zoom-out": "Zoom Out",
"create-widget": "Create widget",
"create-widget-from-symbol": "Create widget from SCADA symbol",
"hidden": "hidden",
"tag": {
"tag": "Tag",
"on-click-action": "On click action",

Loading…
Cancel
Save