SCADA Symbol editor: Add XML editor mode.
This commit is contained in:
		
							parent
							
								
									9e8ca44d44
								
							
						
					
					
						commit
						174166d9a1
					
				@ -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/",
 | 
			
		||||
 | 
			
		||||
@ -289,13 +289,24 @@ const updateScadaSymbolMetadataInDom = (svgDoc: Document, metadata: ScadaSymbolM
 | 
			
		||||
  metadataElement.appendChild(cdata);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const tbMetadataRegex = /<tb:metadata>.*<\/tb:metadata>/gs;
 | 
			
		||||
const tbMetadataRegex = /<tb:metadata[^>]*>.*<\/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) {
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,6 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
<ng-template #noTags>
 | 
			
		||||
  <span fxLayoutAlign="start center"
 | 
			
		||||
  <span fxLayoutAlign="center center"
 | 
			
		||||
        class="tb-prompt">{{ 'scada.tag.no-tags' | translate }}</span>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -15,13 +15,19 @@
 | 
			
		||||
    limitations under the License.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div class="tb-scada-symbol-editor-shape" #scadaSymbolShape></div>
 | 
			
		||||
<div class="tb-scada-symbol-editor-tooltips" #tooltipsContainer>
 | 
			
		||||
<div class="tb-scada-symbol-editor-shape" [fxShow]="editorMode === 'svg'" #scadaSymbolShape></div>
 | 
			
		||||
<div class="tb-scada-symbol-editor-tooltips" [fxShow]="editorMode === 'svg'" #tooltipsContainer>
 | 
			
		||||
  <tb-anchor #tooltipsContainerComponent></tb-anchor>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="tb-scada-symbol-editor-svg-xml" [fxShow]="editorMode === 'xml'">
 | 
			
		||||
  <tb-svg-xml [formControl]="svgContentFormControl"
 | 
			
		||||
              fillHeight
 | 
			
		||||
              noLabel>
 | 
			
		||||
  </tb-svg-xml>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="tb-scada-symbol-editor-buttons">
 | 
			
		||||
  <div class="tb-scada-symbol-editor-view-buttons">
 | 
			
		||||
    <div class="tb-scada-symbol-editor-zoom-buttons tb-primary-fill">
 | 
			
		||||
    <div class="tb-scada-symbol-editor-zoom-buttons tb-primary-fill" [fxShow]="editorMode === 'svg'">
 | 
			
		||||
      <button mat-icon-button
 | 
			
		||||
              [disabled]="zoomInDisabled"
 | 
			
		||||
              (click)="zoomIn()"
 | 
			
		||||
@ -37,7 +43,7 @@
 | 
			
		||||
        <mat-icon>remove</mat-icon>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div *ngIf="displayShowHidden" class="tb-primary-fill">
 | 
			
		||||
    <div *ngIf="displayShowHidden" [fxShow]="editorMode === 'svg'" class="tb-primary-fill">
 | 
			
		||||
      <button mat-icon-button
 | 
			
		||||
              (click)="toggleShowHidden()"
 | 
			
		||||
              matTooltip="{{ (showHiddenElements ? 'scada.hide-hidden-elements' : 'scada.show-hidden-elements') | translate }}"
 | 
			
		||||
@ -46,7 +52,7 @@
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-scada-symbol-editor-upload-buttons">
 | 
			
		||||
  <div class="tb-scada-symbol-editor-right-buttons">
 | 
			
		||||
    <div class="tb-primary-fill">
 | 
			
		||||
      <button mat-icon-button
 | 
			
		||||
              (click)="updateScadaSymbol.emit()"
 | 
			
		||||
@ -63,5 +69,14 @@
 | 
			
		||||
        <mat-icon>download</mat-icon>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <tb-toggle-select appearance="fill"
 | 
			
		||||
                      [disabled]="!svgContentFormControl.valid"
 | 
			
		||||
                      fillHeight
 | 
			
		||||
                      extraPadding
 | 
			
		||||
                      primaryBackground
 | 
			
		||||
                      [(ngModel)]="editorMode">
 | 
			
		||||
      <tb-toggle-option value="svg">{{ 'scada.mode-svg' | translate }}</tb-toggle-option>
 | 
			
		||||
      <tb-toggle-option value="xml">{{ 'scada.mode-xml' | translate }}</tb-toggle-option>
 | 
			
		||||
    </tb-toggle-select>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -85,7 +85,7 @@
 | 
			
		||||
                formControlName="metadata">
 | 
			
		||||
                <div class="tb-scada-symbol-metadata-header-prefix">
 | 
			
		||||
                  <button fxHide.gt-sm
 | 
			
		||||
                          [disabled]="scadaSymbolFormGroup.invalid"
 | 
			
		||||
                          [disabled]="scadaSymbolFormGroup.invalid || !symbolEditorValid"
 | 
			
		||||
                          mat-button color="primary"
 | 
			
		||||
                          (click)="enterPreviewMode()">
 | 
			
		||||
                    <mat-icon>visibility</mat-icon>
 | 
			
		||||
@ -94,7 +94,7 @@
 | 
			
		||||
                </div>
 | 
			
		||||
                <div fxFlex.lt-md class="tb-scada-symbol-metadata-header-suffix" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="12px">
 | 
			
		||||
                  <button fxHide.lt-md
 | 
			
		||||
                          [disabled]="scadaSymbolFormGroup.invalid"
 | 
			
		||||
                          [disabled]="scadaSymbolFormGroup.invalid || !symbolEditorValid"
 | 
			
		||||
                          mat-button color="primary"
 | 
			
		||||
                          (click)="enterPreviewMode()">
 | 
			
		||||
                    <mat-icon>visibility</mat-icon>
 | 
			
		||||
@ -107,7 +107,7 @@
 | 
			
		||||
                      <mat-icon>close</mat-icon>
 | 
			
		||||
                      {{ 'action.decline' | translate }}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button [disabled]="!scadaSymbolFormGroup.valid || !(scadaSymbolFormGroup.dirty || symbolEditorDirty)"
 | 
			
		||||
                    <button [disabled]="!scadaSymbolFormGroup.valid || !symbolEditorValid || !(scadaSymbolFormGroup.dirty || symbolEditorDirty)"
 | 
			
		||||
                            mat-flat-button color="primary"
 | 
			
		||||
                            (click)="onApplyScadaSymbolConfig()">
 | 
			
		||||
                      <mat-icon>done</mat-icon>
 | 
			
		||||
 | 
			
		||||
@ -135,6 +135,8 @@ export class ScadaSymbolComponent extends PageComponent
 | 
			
		||||
 | 
			
		||||
  symbolEditorDirty = false;
 | 
			
		||||
 | 
			
		||||
  symbolEditorValid = true;
 | 
			
		||||
 | 
			
		||||
  private previewScadaSymbolObjectSettings: ScadaSymbolObjectSettings;
 | 
			
		||||
 | 
			
		||||
  private forcePristine = false;
 | 
			
		||||
@ -338,6 +340,10 @@ export class ScadaSymbolComponent extends PageComponent
 | 
			
		||||
    this.symbolEditorDirty = dirty;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSymbolEditObjectValid(valid: boolean) {
 | 
			
		||||
    this.symbolEditorValid = valid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateScadaSymbol() {
 | 
			
		||||
    this.dialog.open<UploadImageDialogComponent, UploadImageDialogData,
 | 
			
		||||
      UploadImageDialogResult>(UploadImageDialogComponent, {
 | 
			
		||||
@ -354,6 +360,7 @@ export class ScadaSymbolComponent extends PageComponent
 | 
			
		||||
          scadaSymbolContent: this.symbolData.scadaSymbolContent
 | 
			
		||||
        };
 | 
			
		||||
        this.symbolEditorDirty = true;
 | 
			
		||||
        this.symbolEditorValid = true;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -471,6 +478,7 @@ export class ScadaSymbolComponent extends PageComponent
 | 
			
		||||
    }
 | 
			
		||||
    this.scadaSymbolFormGroup.markAsPristine();
 | 
			
		||||
    this.symbolEditorDirty = false;
 | 
			
		||||
    this.symbolEditorValid = true;
 | 
			
		||||
    this.cd.markForCheck();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div class="tb-svg-xml" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight, 'no-label': noLabel}"
 | 
			
		||||
     tb-fullscreen
 | 
			
		||||
     [fullscreen]="fullscreen" fxLayout="column">
 | 
			
		||||
  <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;" class="tb-svg-xml-toolbar">
 | 
			
		||||
    <label *ngIf="!noLabel" class="tb-title no-padding" [ngClass]="{'tb-error': !disabled && (hasErrors || required && !modelValue), 'tb-required': !disabled && required}">{{ label }}</label>
 | 
			
		||||
    <span fxFlex></span>
 | 
			
		||||
    <fieldset style="width: initial">
 | 
			
		||||
      <div matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
 | 
			
		||||
           matTooltipPosition="above"
 | 
			
		||||
           style="border-radius: 50%"
 | 
			
		||||
           (click)="fullscreen = !fullscreen">
 | 
			
		||||
        <button type='button' mat-icon-button class="tb-mat-32">
 | 
			
		||||
          <mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div id="tb-svg-xml-panel" class="tb-svg-xml-content-panel" fxLayout="column">
 | 
			
		||||
    <div #svgXmlEditor id="tb-svg-xml-input" [ngStyle]="fillHeight ? {} : {minHeight: minHeight}" [ngClass]="{'fill-height': fillHeight}"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										85
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										211
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								ui-ngx/src/app/shared/components/svg-xml.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -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<AppState>,
 | 
			
		||||
              private raf: RafService,
 | 
			
		||||
              private cd: ChangeDetectorRef) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    const editorElement = this.svgXmlEditorElmRef.nativeElement;
 | 
			
		||||
    let editorOptions: Partial<Ace.EditorOptions> = {
 | 
			
		||||
      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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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">
 | 
			
		||||
  <mat-icon>chevron_left</mat-icon>
 | 
			
		||||
</button>
 | 
			
		||||
<div #toggleGroupContainer class="tb-toggle-container" [class]="{'tb-disable-pagination': disablePagination}" *ngIf="(useSelect$ | async) === false; else select" >
 | 
			
		||||
<div #toggleGroupContainer class="tb-toggle-container"
 | 
			
		||||
     [class.tb-disable-pagination]="disablePagination"
 | 
			
		||||
     [class.fill-height]="fillHeight"
 | 
			
		||||
     [class.extra-padding]="extraPadding"
 | 
			
		||||
     *ngIf="(useSelect$ | async) === false; else select" >
 | 
			
		||||
  <mat-button-toggle-group #toggleGroup
 | 
			
		||||
                           class="tb-toggle-header"
 | 
			
		||||
                           [ngClass]="{'tb-fill': (appearance === 'fill' || appearance === 'fill-invert'),
 | 
			
		||||
                                       'tb-invert': appearance === 'fill-invert',
 | 
			
		||||
                                       'tb-ignore-md-lg': ignoreMdLgSize,
 | 
			
		||||
                                       'tb-disabled': disabled }" [name]="name" [(ngModel)]="value"
 | 
			
		||||
                           [class.tb-fill]="(appearance === 'fill' || appearance === 'fill-invert')"
 | 
			
		||||
                           [class.tb-invert]="appearance === 'fill-invert'"
 | 
			
		||||
                           [class.tb-primary-fill]="primaryBackground"
 | 
			
		||||
                           [class.tb-ignore-md-lg]="ignoreMdLgSize"
 | 
			
		||||
                           [class.tb-disabled]="disabled"
 | 
			
		||||
                           [name]="name"
 | 
			
		||||
                           [(ngModel)]="value"
 | 
			
		||||
                           (ngModelChange)="valueChange.emit(value)">
 | 
			
		||||
    <mat-button-toggle *ngFor="let option of options; trackBy: trackByHeaderOption" [value]="option.value" [disabled]="disabled">{{ option.name }}</mat-button-toggle>
 | 
			
		||||
  </mat-button-toggle-group>
 | 
			
		||||
@ -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">
 | 
			
		||||
  <mat-icon>chevron_right</mat-icon>
 | 
			
		||||
</button>
 | 
			
		||||
<ng-template #select>
 | 
			
		||||
  <mat-form-field appearance="outline" class="tb-toggle-header-select" subscriptSizing="dynamic">
 | 
			
		||||
  <mat-form-field appearance="outline" class="tb-toggle-header-select"
 | 
			
		||||
                  [class.fill-height]="fillHeight"
 | 
			
		||||
                  subscriptSizing="dynamic">
 | 
			
		||||
    <mat-select [(ngModel)]="value" (ngModelChange)="valueChange.emit($event)" [disabled]="disabled">
 | 
			
		||||
      <mat-option *ngFor="let option of options" [value]="option.value"> {{ option.name }}</mat-option>
 | 
			
		||||
    </mat-select>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,14 @@
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<tb-toggle-header
 | 
			
		||||
  [class.fill-height]="fillHeight"
 | 
			
		||||
  ignoreMdLgSize="true"
 | 
			
		||||
  useSelectOnMdLg="false"
 | 
			
		||||
  [disabled]="disabled"
 | 
			
		||||
  [appearance]="appearance"
 | 
			
		||||
  [fillHeight]="fillHeight"
 | 
			
		||||
  [extraPadding]="extraPadding"
 | 
			
		||||
  [primaryBackground]="primaryBackground"
 | 
			
		||||
  [disablePagination]="disablePagination"
 | 
			
		||||
  [selectMediaBreakpoint]="selectMediaBreakpoint"
 | 
			
		||||
  [options]="options"
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
:host {
 | 
			
		||||
  tb-toggle-header {
 | 
			
		||||
    &.fill-height {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -24,7 +24,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-toggle-select',
 | 
			
		||||
  templateUrl: './toggle-select.component.html',
 | 
			
		||||
  styleUrls: [],
 | 
			
		||||
  styleUrls: ['./toggle-select.component.scss'],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
@ -52,6 +52,18 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  disablePagination = false;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  fillHeight = false;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  extraPadding = false;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  primaryBackground = false;
 | 
			
		||||
 | 
			
		||||
  modelValue: any;
 | 
			
		||||
 | 
			
		||||
  private propagateChange = null;
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,8 @@ function loadAceDependencies(): Observable<any> {
 | 
			
		||||
    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<any> {
 | 
			
		||||
    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')));
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user