diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 36b2b4b2f1..10c904f88d 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -39,6 +39,11 @@ "input": "./node_modules/ace-builds/src-noconflict/", "output": "/" }, + { + "glob": "worker-xml.js", + "input": "./node_modules/ace-builds/src-noconflict/", + "output": "/" + }, { "glob": "worker-css.js", "input": "./node_modules/ace-builds/src-noconflict/", diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index 3c4e32abf7..ff74bc58ac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -289,13 +289,24 @@ const updateScadaSymbolMetadataInDom = (svgDoc: Document, metadata: ScadaSymbolM metadataElement.appendChild(cdata); }; -const tbMetadataRegex = /.*<\/tb:metadata>/gs; +const tbMetadataRegex = /]*>.*<\/tb:metadata>/gs; export interface ScadaSymbolContentData { svgRootNode: string; innerSvg: string; } +export const removeScadaSymbolMetadata = (svgContent: string): string => { + let result = svgContent; + tbMetadataRegex.lastIndex = 0; + const metadataMatch = tbMetadataRegex.exec(svgContent); + if (metadataMatch !== null && metadataMatch.length) { + const metadata = metadataMatch[0]; + result = result.replace(metadata, ''); + } + return result; +}; + export const scadaSymbolContentData = (svgContent: string): ScadaSymbolContentData => { const result: ScadaSymbolContentData = { svgRootNode: '', @@ -517,10 +528,12 @@ export class ScadaSymbolObject { if (this.context) { for (const tag of this.metadata.tags) { const elements = this.context.tags[tag.tag]; - elements.forEach(element => { - element.timeline().stop(); - element.timeline(null); - }); + if (elements) { + elements.forEach(element => { + element.timeline().stop(); + element.timeline(null); + }); + } } } if (this.svgShape) { 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 ec0f69a7f0..32bb345ecf 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 @@ -35,6 +35,6 @@ - {{ 'scada.tag.no-tags' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html index 4eaaf6d718..96bd1d7a8a 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html @@ -15,13 +15,19 @@ limitations under the License. --> -
-
+
+
+
+ + +
-
+
-
+
-
+
+ + {{ 'scada.mode-svg' | translate }} + {{ 'scada.mode-xml' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss index c400a32fef..b8a16f4c81 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss @@ -25,6 +25,11 @@ inset: 0; pointer-events: none; } +.tb-scada-symbol-editor-svg-xml { + width: 100%; + height: 100%; + padding-top: 88px; +} .tb-scada-symbol-editor-buttons { pointer-events: none; position: absolute; @@ -64,11 +69,14 @@ } } - .tb-scada-symbol-editor-upload-buttons { + .tb-scada-symbol-editor-right-buttons { display: flex; flex-direction: row; gap: 8px; z-index: 101; + tb-toggle-select { + pointer-events: auto; + } } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts index 9228c3e561..97e9305470 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts @@ -34,11 +34,15 @@ import { ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol-editor.models'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { FormControl } from '@angular/forms'; +import { removeScadaSymbolMetadata } from '@home/components/widget/lib/scada/scada-symbol.models'; export interface ScadaSymbolEditorData { scadaSymbolContent: string; } +type editorModeType = 'svg' | 'xml'; + @Component({ selector: 'tb-scada-symbol-editor', templateUrl: './scada-symbol-editor.component.html', @@ -84,10 +88,31 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI displayShowHidden = false; + svgContentFormControl = new FormControl(); + + svgContent: string; + + private editorModeValue: editorModeType = 'svg'; + + get editorMode(): editorModeType { + return this.editorModeValue; + } + + set editorMode(value: editorModeType) { + this.updateEditorMode(value); + } + constructor(private cd: ChangeDetectorRef) { } ngOnInit(): void { + this.svgContentFormControl.valueChanges.subscribe((svgContent) => { + if (this.svgContent !== svgContent) { + this.svgContent = svgContent; + this.editObjectCallbacks.onSymbolEditObjectDirty(true); + } + this.editObjectCallbacks.onSymbolEditObjectValid(this.svgContentFormControl.valid); + }); } ngAfterViewInit() { @@ -114,13 +139,16 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI const change = changes[propName]; if (!change.firstChange && change.currentValue !== change.previousValue) { if (propName === 'data') { - if (this.scadaSymbolEditObject) { - setTimeout(() => { - this.updateContent(this.data.scadaSymbolContent); - }); - } + setTimeout(() => { + this.updateContent(this.data.scadaSymbolContent); + }); } else if (propName === 'readonly') { this.scadaSymbolEditObject.setReadOnly(this.readonly); + if (this.readonly) { + this.svgContentFormControl.disable({emitEvent: false}); + } else { + this.svgContentFormControl.enable({emitEvent: false}); + } } } } @@ -131,7 +159,11 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI } getContent(): string { - return this.scadaSymbolEditObject?.getContent(); + if (this.editorMode === 'svg') { + return this.scadaSymbolEditObject?.getContent(); + } else { + return this.svgContent; + } } zoomIn() { @@ -148,12 +180,33 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI this.scadaSymbolEditObject.showHiddenElements(this.showHiddenElements); } + private updateEditorMode(mode: editorModeType) { + this.editorModeValue = mode; + if (mode === 'xml') { + this.svgContent = this.scadaSymbolEditObject.getContent(); + this.svgContentFormControl.setValue(this.svgContent, {emitEvent: false}); + } else { + this.updateEditObjectContent(this.svgContent); + } + } + private updateContent(content: string) { - this.displayShowHidden = false; - this.scadaSymbolEditObject.setContent(content); - setTimeout(() => { - this.updateZoomButtonsState(); - }); + this.svgContent = removeScadaSymbolMetadata(content); + if (this.editorMode === 'xml') { + this.svgContentFormControl.setValue(this.svgContent, {emitEvent: false}); + } else { + this.updateEditObjectContent(this.svgContent); + } + } + + private updateEditObjectContent(content: string) { + if (this.scadaSymbolEditObject) { + this.displayShowHidden = false; + this.scadaSymbolEditObject.setContent(content); + setTimeout(() => { + this.updateZoomButtonsState(); + }); + } } private updateZoomButtonsState() { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts index f3caf1dbfc..ddc935d202 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts @@ -54,6 +54,7 @@ export interface ScadaSymbolEditObjectCallbacks { tagsUpdated: (tags: string[]) => void; hasHiddenElements?: (hasHidden: boolean) => void; onSymbolEditObjectDirty: (dirty: boolean) => void; + onSymbolEditObjectValid: (valid: boolean) => void; onZoom?: () => void; } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html index 6662509fa6..abd015731e 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html @@ -85,7 +85,7 @@ formControlName="metadata"> + +
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/svg-xml.component.scss b/ui-ngx/src/app/shared/components/svg-xml.component.scss new file mode 100644 index 0000000000..8ccb5f1f7d --- /dev/null +++ b/ui-ngx/src/app/shared/components/svg-xml.component.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2024 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. + */ +.tb-svg-xml { + position: relative; + + &.tb-disabled { + color: rgba(0, 0, 0, .38); + } + + &.fill-height { + height: 100%; + } + + &.no-label { + .tb-svg-xml-content-panel { + height: 100%; + } + .tb-svg-xml-toolbar { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 10; + pointer-events: none; + button { + pointer-events: auto; + } + } + } + + .tb-svg-xml-content-panel { + height: calc(100% - 40px); + border: 1px solid #c0c0c0; + + #tb-svg-xml-input { + width: 100%; + min-width: 200px; + height: 100%; + } + } + + &:not(.tb-fullscreen) { + padding-bottom: 15px; + } + + .tb-svg-xml-toolbar { + & > * { + &:not(:last-child) { + margin-right: 4px; + } + } + button.mat-mdc-button-base, button.mat-mdc-button-base.tb-mat-32 { + background: rgba(220, 220, 220, .35); + align-items: center; + vertical-align: middle; + min-width: 32px; + min-height: 15px; + padding: 4px; + font-size: .8rem; + line-height: 15px; + &:not(.tb-help-popup-button) { + color: #7b7b7b; + } + } + button.mat-mdc-button-base:not(.mat-mdc-icon-button) { + height: 23px; + } + .tb-help-popup-button-loading { + background: #f3f3f3; + } + } +} diff --git a/ui-ngx/src/app/shared/components/svg-xml.component.ts b/ui-ngx/src/app/shared/components/svg-xml.component.ts new file mode 100644 index 0000000000..ce57b8d998 --- /dev/null +++ b/ui-ngx/src/app/shared/components/svg-xml.component.ts @@ -0,0 +1,211 @@ +/// +/// Copyright © 2016-2024 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 { + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, UntypedFormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; +import { Ace } from 'ace-builds'; +import { getAce } from '@shared/models/ace/ace.models'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-svg-xml', + templateUrl: './svg-xml.component.html', + styleUrls: ['./svg-xml.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SvgXmlComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SvgXmlComponent), + multi: true, + } + ], + encapsulation: ViewEncapsulation.None +}) +export class SvgXmlComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { + + @ViewChild('svgXmlEditor', {static: true}) + svgXmlEditorElmRef: ElementRef; + + private svgXmlEditor: Ace.Editor; + private editorsResizeCaf: CancelAnimationFrame; + private editorResize$: ResizeObserver; + private ignoreChange = false; + + @Input() label: string; + + @Input() disabled: boolean; + + @Input() + @coerceBoolean() + fillHeight = false; + + @Input() + @coerceBoolean() + noLabel = false; + + @Input() minHeight = '200px'; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + fullscreen = false; + + modelValue: string; + + hasErrors = false; + + private propagateChange = null; + + constructor(public elementRef: ElementRef, + private utils: UtilsService, + private translate: TranslateService, + protected store: Store, + private raf: RafService, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + const editorElement = this.svgXmlEditorElmRef.nativeElement; + let editorOptions: Partial = { + mode: 'ace/mode/svg', + showGutter: true, + showPrintMargin: true, + readOnly: this.disabled + }; + + const advancedOptions = { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }; + + editorOptions = {...editorOptions, ...advancedOptions}; + getAce().subscribe( + (ace) => { + this.svgXmlEditor = ace.edit(editorElement, editorOptions); + this.svgXmlEditor.session.setUseWrapMode(true); + this.svgXmlEditor.setValue(this.modelValue ? this.modelValue : '', -1); + this.svgXmlEditor.setReadOnly(this.disabled); + this.svgXmlEditor.on('change', () => { + if (!this.ignoreChange) { + this.updateView(); + } + }); + // @ts-ignore + this.svgXmlEditor.session.on('changeAnnotation', () => { + const annotations = this.svgXmlEditor.session.getAnnotations(); + const hasErrors = annotations.filter(annotation => annotation.type === 'error').length > 0; + if (this.hasErrors !== hasErrors) { + this.hasErrors = hasErrors; + this.propagateChange(this.modelValue); + this.cd.markForCheck(); + } + }); + this.editorResize$ = new ResizeObserver(() => { + this.onAceEditorResize(); + }); + this.editorResize$.observe(editorElement); + } + ); + } + + ngOnDestroy(): void { + if (this.editorResize$) { + this.editorResize$.disconnect(); + } + if (this.svgXmlEditor) { + this.svgXmlEditor.destroy(); + } + } + + private onAceEditorResize() { + if (this.editorsResizeCaf) { + this.editorsResizeCaf(); + this.editorsResizeCaf = null; + } + this.editorsResizeCaf = this.raf.raf(() => { + this.svgXmlEditor.resize(); + this.svgXmlEditor.renderer.updateFull(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.svgXmlEditor) { + this.svgXmlEditor.setReadOnly(this.disabled); + } + } + + public validate(c: UntypedFormControl) { + return (!this.hasErrors) ? null : { + svgXml: { + valid: false, + }, + }; + } + + writeValue(value: string): void { + this.modelValue = value; + if (this.svgXmlEditor) { + this.ignoreChange = true; + this.svgXmlEditor.setValue(this.modelValue ? this.modelValue : '', -1); + this.ignoreChange = false; + } + } + + updateView() { + const editorValue = this.svgXmlEditor.getValue(); + if (this.modelValue !== editorValue) { + this.modelValue = editorValue; + this.propagateChange(this.modelValue); + this.cd.markForCheck(); + } + } +} diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.html b/ui-ngx/src/app/shared/components/toggle-header.component.html index 225ccfe141..ff85b45de1 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.html +++ b/ui-ngx/src/app/shared/components/toggle-header.component.html @@ -20,16 +20,26 @@ [disabled]="!leftPaginationEnabled" (click)="handlePaginatorClick('before', $event)" (touchstart)="handlePaginatorTouchStart('before', $event)" - class="tb-toggle-header-pagination-button" [class]="{'tb-mat-32': !isMdLg, 'tb-mat-24': isMdLg}"> + class="tb-toggle-header-pagination-button" + [class.fill-height]="fillHeight" + [class.tb-mat-32]="!isMdLg" + [class.tb-mat-24]="isMdLg"> chevron_left -
+
{{ option.name }} @@ -39,11 +49,16 @@ [disabled]="!rightPaginationEnabled" (click)="handlePaginatorClick('after', $event)" (touchstart)="handlePaginatorTouchStart('after', $event)" - class="tb-toggle-header-pagination-button" [class]="{'tb-mat-32': !isMdLg, 'tb-mat-24': isMdLg}"> + class="tb-toggle-header-pagination-button" + [class.fill-height]="fillHeight" + [class.tb-mat-32]="!isMdLg" + [class.tb-mat-24]="isMdLg"> chevron_right - + {{ option.name }} diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index 0c59f4f919..2de4e14209 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -21,6 +21,9 @@ grid-template-columns: min-content minmax(auto, 1fr) min-content; .tb-toggle-header-pagination-button { display: none; + &.fill-height { + height: 100%; + } } &.tb-toggle-header-pagination-controls-enabled { .tb-toggle-header-pagination-button { @@ -43,6 +46,25 @@ } :host ::ng-deep { + .tb-toggle-container { + &.fill-height { + .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { + height: 100%; + .mat-button-toggle.mat-button-toggle-appearance-standard { + .mat-button-toggle-button { + height: 100%; + } + } + } + } + &.extra-padding { + .mat-button-toggle.mat-button-toggle-appearance-standard { + .mat-button-toggle-label-content { + padding: 0 20px; + } + } + } + } .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { overflow: visible; width: 100%; @@ -51,6 +73,12 @@ padding: 2px; border: none; background: rgba(0, 0, 0, 0.06); + &.tb-primary-fill { + background: none; + &:before { + border-radius: 100px; + } + } .mat-button-toggle + .mat-button-toggle { border-left: none; } @@ -161,6 +189,16 @@ &.mat-mdc-form-field { line-height: 16px; font-size: 12px; + &.fill-height { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + margin: auto; + } + } + .mat-mdc-form-field-subscript-wrapper { + display: none; + } + } } .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { min-height: 0; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index eef0f8bd74..92ccbb76f9 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -187,6 +187,18 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @coerceBoolean() disabled = false; + @Input() + @coerceBoolean() + fillHeight = false; + + @Input() + @coerceBoolean() + extraPadding = false; + + @Input() + @coerceBoolean() + primaryBackground = false; + get isMdLg(): boolean { return !this.ignoreMdLgSize && this.isMdLgValue; } diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.html b/ui-ngx/src/app/shared/components/toggle-select.component.html index c5c762d17f..f0d8172f41 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.html +++ b/ui-ngx/src/app/shared/components/toggle-select.component.html @@ -16,10 +16,14 @@ --> { aceObservables.push(from(import('ace-builds/src-noconflict/mode-text'))); aceObservables.push(from(import('ace-builds/src-noconflict/mode-markdown'))); aceObservables.push(from(import('ace-builds/src-noconflict/mode-html'))); + aceObservables.push(from(import('ace-builds/src-noconflict/mode-xml'))); + aceObservables.push(from(import('ace-builds/src-noconflict/mode-svg'))); aceObservables.push(from(import('ace-builds/src-noconflict/mode-c_cpp'))); aceObservables.push(from(import('ace-builds/src-noconflict/mode-protobuf'))); aceObservables.push(from(import('ace-builds/src-noconflict/snippets/java'))); @@ -47,6 +49,8 @@ function loadAceDependencies(): Observable { aceObservables.push(from(import('ace-builds/src-noconflict/snippets/text'))); aceObservables.push(from(import('ace-builds/src-noconflict/snippets/markdown'))); aceObservables.push(from(import('ace-builds/src-noconflict/snippets/html'))); + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/xml'))); + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/svg'))); aceObservables.push(from(import('ace-builds/src-noconflict/snippets/c_cpp'))); aceObservables.push(from(import('ace-builds/src-noconflict/snippets/protobuf'))); aceObservables.push(from(import('ace-builds/src-noconflict/theme-textmate'))); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 0ed819b248..2bf3676b32 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -224,6 +224,7 @@ import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol-input.component'; import { CountryAutocompleteComponent } from '@shared/components/country-autocomplete.component'; import { CountryData } from '@shared/models/country.models'; +import { SvgXmlComponent } from '@shared/components/svg-xml.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -338,6 +339,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) JsFuncComponent, CssComponent, HtmlComponent, + SvgXmlComponent, FabTriggerDirective, FabActionsDirective, FabToolbarComponent, @@ -544,6 +546,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) JsFuncComponent, CssComponent, HtmlComponent, + SvgXmlComponent, FabTriggerDirective, FabActionsDirective, TbJsonToStringDirective, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0d32107f8d..369985740d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3661,6 +3661,8 @@ "update-symbol": "Update SCADA symbol", "edit-symbol": "Edit SCADA symbol", "symbol-details": "SCADA symbol details", + "mode-svg": "SVG", + "mode-xml": "XML", "no-symbols": "No symbols found", "show-hidden-elements": "Show hidden elements", "hide-hidden-elements": "Hide hidden elements",