diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html
index 8e1680ea46..24b6ff099b 100644
--- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html
+++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html
@@ -47,35 +47,50 @@
-
- device-profile.telemetry-proto-schema
-
+
+
+
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
-
-
- device-profile.attributes-proto-schema
-
+
+
+
+
{{ 'device-profile.attributes-proto-schema-required' | translate}}
-
-
- device-profile.rpc-request-proto-schema
-
+
+
+
+
{{ 'device-profile.rpc-request-proto-schema-required' | translate}}
- device-profile.rpc-request-proto-schema-hint
-
-
- device-profile.rpc-response-proto-schema
-
+
+
+
+
{{ 'device-profile.rpc-response-proto-schema-required' | translate}}
-
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
index 5f49053c42..5b597a1d47 100644
--- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
+++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
@@ -86,35 +86,50 @@
-
- device-profile.telemetry-proto-schema
-
+
+
+
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
-
-
- device-profile.attributes-proto-schema
-
+
+
+
+
{{ 'device-profile.attributes-proto-schema-required' | translate}}
-
-
- device-profile.rpc-request-proto-schema
-
+
+
+
+
{{ 'device-profile.rpc-request-proto-schema-required' | translate}}
- device-profile.rpc-request-proto-schema-hint
-
-
- device-profile.rpc-response-proto-schema
-
+
+
+
+
{{ 'device-profile.rpc-response-proto-schema-required' | translate}}
-
+
diff --git a/ui-ngx/src/app/shared/components/protobuf-content.component.html b/ui-ngx/src/app/shared/components/protobuf-content.component.html
new file mode 100644
index 0000000000..23a16ece43
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/protobuf-content.component.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/protobuf-content.component.scss b/ui-ngx/src/app/shared/components/protobuf-content.component.scss
new file mode 100644
index 0000000000..369d172491
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/protobuf-content.component.scss
@@ -0,0 +1,57 @@
+/**
+ * Copyright © 2016-2021 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 {
+ position: relative;
+
+ .fill-height {
+ height: 100%;
+ }
+}
+
+.tb-protobuf-content-toolbar {
+ button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 {
+ align-items: center;
+ vertical-align: middle;
+ min-width: 32px;
+ min-height: 15px;
+ padding: 4px;
+ margin: 0;
+ font-size: .8rem;
+ line-height: 15px;
+ color: #7b7b7b;
+ background: rgba(220, 220, 220, .35);
+ &:not(:last-child) {
+ margin-right: 4px;
+ }
+ }
+}
+
+.tb-protobuf-content-panel {
+ height: 100%;
+ margin-left: 15px;
+ border: 1px solid #c0c0c0;
+
+ #tb-protobuf-input {
+ width: 100%;
+ min-width: 200px;
+ min-height: 160px;
+ height: 100%;
+
+ &:not(.fill-height) {
+ min-height: 200px;
+ }
+ }
+}
diff --git a/ui-ngx/src/app/shared/components/protobuf-content.component.ts b/ui-ngx/src/app/shared/components/protobuf-content.component.ts
new file mode 100644
index 0000000000..0c1e7c7fac
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/protobuf-content.component.ts
@@ -0,0 +1,193 @@
+///
+/// Copyright © 2016-2021 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 {
+ Component,
+ ElementRef,
+ forwardRef,
+ Input,
+ OnDestroy,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { Ace } from 'ace-builds';
+import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
+import { ResizeObserver } from '@juggle/resize-observer';
+import { guid } from '@core/utils';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import { getAce } from '@shared/models/ace/ace.models';
+import { beautifyJs } from '@shared/models/beautify.models';
+
+@Component({
+ selector: 'tb-protobuf-content',
+ templateUrl: './protobuf-content.component.html',
+ styleUrls: ['./protobuf-content.component.scss'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => ProtobufContentComponent),
+ multi: true
+ }
+ ]
+})
+export class ProtobufContentComponent implements OnInit, ControlValueAccessor, OnDestroy {
+
+ @ViewChild('protobufEditor', {static: true})
+ protobufEditorElmRef: ElementRef;
+
+ private protobufEditor: Ace.Editor;
+ private editorsResizeCaf: CancelAnimationFrame;
+ private editorResize$: ResizeObserver;
+ private ignoreChange = false;
+
+ toastTargetId = `protobufContentEditor-${guid()}`;
+
+ @Input() label: string;
+
+ @Input() disabled: boolean;
+
+ @Input() fillHeight: boolean;
+
+ @Input() editorStyle: {[klass: string]: any};
+
+ @Input() tbPlaceholder: string;
+
+ private readonlyValue: boolean;
+ get readonly(): boolean {
+ return this.readonlyValue;
+ }
+ @Input()
+ set readonly(value: boolean) {
+ this.readonlyValue = coerceBooleanProperty(value);
+ }
+
+ fullscreen = false;
+
+ contentBody: string;
+
+ errorShowed = false;
+
+ private propagateChange = null;
+
+ constructor(public elementRef: ElementRef,
+ protected store: Store,
+ private raf: RafService) {
+ }
+
+ ngOnInit(): void {
+ const editorElement = this.protobufEditorElmRef.nativeElement;
+ let editorOptions: Partial = {
+ mode: `ace/mode/protobuf`,
+ showGutter: true,
+ showPrintMargin: false,
+ readOnly: this.disabled || this.readonly,
+ };
+
+ const advancedOptions = {
+ enableSnippets: true,
+ enableBasicAutocompletion: true,
+ enableLiveAutocompletion: true
+ };
+
+ editorOptions = {...editorOptions, ...advancedOptions};
+ getAce().subscribe(
+ (ace) => {
+ this.protobufEditor = ace.edit(editorElement, editorOptions);
+ this.protobufEditor.session.setUseWrapMode(true);
+ this.protobufEditor.setValue(this.contentBody ? this.contentBody : '', -1);
+ this.protobufEditor.setReadOnly(this.disabled || this.readonly);
+ this.protobufEditor.on('change', () => {
+ if (!this.ignoreChange) {
+ this.updateView();
+ }
+ });
+ this.editorResize$ = new ResizeObserver(() => {
+ this.onAceEditorResize();
+ });
+ this.editorResize$.observe(editorElement);
+ }
+ );
+ }
+
+ ngOnDestroy(): void {
+ if (this.editorResize$) {
+ this.editorResize$.disconnect();
+ }
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ if (this.protobufEditor) {
+ this.protobufEditor.setReadOnly(this.disabled || this.readonly);
+ }
+ }
+
+ writeValue(value: string): void {
+ this.contentBody = value;
+ if (this.protobufEditor) {
+ this.ignoreChange = true;
+ this.protobufEditor.setValue(this.contentBody ? this.contentBody : '', -1);
+ this.ignoreChange = false;
+ }
+ }
+
+ updateView() {
+ const editorValue = this.protobufEditor.getValue();
+ if (this.contentBody !== editorValue) {
+ this.contentBody = editorValue;
+ this.propagateChange(this.contentBody);
+ }
+ }
+
+ beautifyProtobuf() {
+ beautifyJs(this.contentBody, {indent_size: 4, wrap_line_length: 60}).subscribe(
+ (res) => {
+ this.protobufEditor.setValue(res ? res : '', -1);
+ this.updateView();
+ }
+ );
+ }
+
+ onFullscreen() {
+ if (this.protobufEditor) {
+ setTimeout(() => {
+ this.protobufEditor.resize();
+ }, 0);
+ }
+ }
+
+ private onAceEditorResize() {
+ if (this.editorsResizeCaf) {
+ this.editorsResizeCaf();
+ this.editorsResizeCaf = null;
+ }
+ this.editorsResizeCaf = this.raf.raf(() => {
+ this.protobufEditor.resize();
+ this.protobufEditor.renderer.updateFull();
+ });
+ }
+
+}
diff --git a/ui-ngx/src/app/shared/models/ace/ace.models.ts b/ui-ngx/src/app/shared/models/ace/ace.models.ts
index f87275e679..9a3c30766e 100644
--- a/ui-ngx/src/app/shared/models/ace/ace.models.ts
+++ b/ui-ngx/src/app/shared/models/ace/ace.models.ts
@@ -36,6 +36,8 @@ export function loadAceDependencies(): Observable {
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-c_cpp')));
+ aceObservables.push(from(import('ace-builds/src-noconflict/mode-protobuf')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/java')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/css')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/json')));
@@ -43,6 +45,8 @@ export 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/c_cpp')));
+ aceObservables.push(from(import('ace-builds/src-noconflict/snippets/protobuf')));
aceObservables.push(from(import('ace-builds/src-noconflict/theme-textmate')));
aceObservables.push(from(import('ace-builds/src-noconflict/theme-github')));
return forkJoin(aceObservables).pipe(
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index 2b1de653ec..90b80393dd 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -155,6 +155,7 @@ import { MarkedOptionsService } from '@shared/components/marked-options.service'
import { TbPopoverService } from '@shared/components/popover.service';
import { HELP_MARKDOWN_COMPONENT_TOKEN, SHARED_MODULE_TOKEN } from '@shared/components/tokens';
import { TbMarkdownComponent } from '@shared/components/markdown.component';
+import { ProtobufContentComponent } from './components/protobuf-content.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@@ -268,7 +269,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
OtaPackageAutocompleteComponent,
WidgetsBundleSearchComponent,
CopyButtonComponent,
- TogglePasswordComponent
+ TogglePasswordComponent,
+ ProtobufContentComponent
],
imports: [
CommonModule,
@@ -458,7 +460,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
OtaPackageAutocompleteComponent,
WidgetsBundleSearchComponent,
CopyButtonComponent,
- TogglePasswordComponent
+ TogglePasswordComponent,
+ ProtobufContentComponent
]
})
export class SharedModule { }