Browse Source

UI: Add HTML card/HTML value card widget settings form

pull/6545/head
Igor Kulikov 5 years ago
parent
commit
323c67ff2a
  1. 10
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 25
      ui-ngx/src/app/modules/home/components/widget/lib/settings/html-card-widget-settings.component.html
  3. 54
      ui-ngx/src/app/modules/home/components/widget/lib/settings/html-card-widget-settings.component.ts
  4. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.html
  5. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.ts
  6. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  7. 2
      ui-ngx/src/app/shared/components/css.component.html
  8. 6
      ui-ngx/src/app/shared/components/css.component.ts
  9. 41
      ui-ngx/src/app/shared/components/html.component.html
  10. 65
      ui-ngx/src/app/shared/components/html.component.scss
  11. 211
      ui-ngx/src/app/shared/components/html.component.ts
  12. 8
      ui-ngx/src/app/shared/components/js-func.component.html
  13. 9
      ui-ngx/src/app/shared/components/js-func.component.ts
  14. 2
      ui-ngx/src/app/shared/components/markdown-editor.component.html
  15. 3
      ui-ngx/src/app/shared/shared.module.ts
  16. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

10
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

25
ui-ngx/src/app/modules/home/components/widget/lib/settings/html-card-widget-settings.component.html

@ -0,0 +1,25 @@
<!--
Copyright © 2016-2022 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.
-->
<section [formGroup]="htmlCardWidgetSettingsForm" fxLayout="column">
<tb-html formControlName="cardHtml" required
label="{{ 'widgets.html-card.html' | translate }}">
</tb-html>
<tb-css formControlName="cardCss"
label="{{ 'widgets.html-card.css' | translate }}">
</tb-css>
</section>

54
ui-ngx/src/app/modules/home/components/widget/lib/settings/html-card-widget-settings.component.ts

@ -0,0 +1,54 @@
///
/// Copyright © 2016-2022 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 } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({
selector: 'tb-html-card-widget-settings',
templateUrl: './html-card-widget-settings.component.html',
styleUrls: []
})
export class HtmlCardWidgetSettingsComponent extends WidgetSettingsComponent {
htmlCardWidgetSettingsForm: FormGroup;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.htmlCardWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {
cardHtml: '<div class=\'card\'>HTML code here</div>',
cardCss: '.card {\n font-weight: bold; \n}'
};
}
protected onSettingsSet(settings: WidgetSettings) {
this.htmlCardWidgetSettingsForm = this.fb.group({
cardHtml: [settings.cardHtml, [Validators.required]],
cardCss: [settings.cardCss, []]
});
}
}

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.html

@ -26,7 +26,8 @@
{{ 'widgets.qr-code.qr-code-text-pattern-required' | translate }} {{ 'widgets.qr-code.qr-code-text-pattern-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-js-func [fxShow]="qrCodeWidgetSettingsForm.get('useQrCodeTextFunction').value" <tb-js-func required
[fxShow]="qrCodeWidgetSettingsForm.get('useQrCodeTextFunction').value"
formControlName="qrCodeTextFunction" formControlName="qrCodeTextFunction"
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
[functionArgs]="['data']" [functionArgs]="['data']"

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.ts

@ -50,7 +50,7 @@ export class QrCodeWidgetSettingsComponent extends WidgetSettingsComponent {
this.qrCodeWidgetSettingsForm = this.fb.group({ this.qrCodeWidgetSettingsForm = this.fb.group({
qrCodeTextPattern: [settings.qrCodeTextPattern, [Validators.required]], qrCodeTextPattern: [settings.qrCodeTextPattern, [Validators.required]],
useQrCodeTextFunction: [settings.useQrCodeTextFunction, [Validators.required]], useQrCodeTextFunction: [settings.useQrCodeTextFunction, [Validators.required]],
qrCodeTextFunction: [settings.qrCodeTextFunction, []] qrCodeTextFunction: [settings.qrCodeTextFunction, [Validators.required]]
}); });
} }

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -44,6 +44,9 @@ import {
import { import {
EntitiesHierarchyWidgetSettingsComponent EntitiesHierarchyWidgetSettingsComponent
} from '@home/components/widget/lib/settings/entities-hierarchy-widget-settings.component'; } from '@home/components/widget/lib/settings/entities-hierarchy-widget-settings.component';
import {
HtmlCardWidgetSettingsComponent
} from '@home/components/widget/lib/settings/html-card-widget-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -57,7 +60,8 @@ import {
LabelWidgetSettingsComponent, LabelWidgetSettingsComponent,
SimpleCardWidgetSettingsComponent, SimpleCardWidgetSettingsComponent,
DashboardStateWidgetSettingsComponent, DashboardStateWidgetSettingsComponent,
EntitiesHierarchyWidgetSettingsComponent EntitiesHierarchyWidgetSettingsComponent,
HtmlCardWidgetSettingsComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -75,7 +79,8 @@ import {
LabelWidgetSettingsComponent, LabelWidgetSettingsComponent,
SimpleCardWidgetSettingsComponent, SimpleCardWidgetSettingsComponent,
DashboardStateWidgetSettingsComponent, DashboardStateWidgetSettingsComponent,
EntitiesHierarchyWidgetSettingsComponent EntitiesHierarchyWidgetSettingsComponent,
HtmlCardWidgetSettingsComponent
] ]
}) })
export class WidgetSettingsModule { export class WidgetSettingsModule {
@ -90,5 +95,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-label-widget-settings': LabelWidgetSettingsComponent, 'tb-label-widget-settings': LabelWidgetSettingsComponent,
'tb-simple-card-widget-settings': SimpleCardWidgetSettingsComponent, 'tb-simple-card-widget-settings': SimpleCardWidgetSettingsComponent,
'tb-dashboard-state-widget-settings': DashboardStateWidgetSettingsComponent, 'tb-dashboard-state-widget-settings': DashboardStateWidgetSettingsComponent,
'tb-entities-hierarchy-widget-settings': EntitiesHierarchyWidgetSettingsComponent 'tb-entities-hierarchy-widget-settings': EntitiesHierarchyWidgetSettingsComponent,
'tb-html-card-widget-settings': HtmlCardWidgetSettingsComponent
}; };

2
ui-ngx/src/app/shared/components/css.component.html

@ -19,7 +19,7 @@
tb-fullscreen tb-fullscreen
[fullscreen]="fullscreen" fxLayout="column"> [fullscreen]="fullscreen" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-css-toolbar"> <div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-css-toolbar">
<label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors}">{{ label }}</label> <label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors || required && !modelValue, 'tb-required': required}">{{ label }}</label>
<span fxFlex></span> <span fxFlex></span>
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyCss()"> <button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyCss()">
{{'js-func.tidy' | translate }} {{'js-func.tidy' | translate }}

6
ui-ngx/src/app/shared/components/css.component.ts

@ -15,6 +15,7 @@
/// ///
import { import {
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
forwardRef, forwardRef,
@ -91,7 +92,8 @@ export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Va
private utils: UtilsService, private utils: UtilsService,
private translate: TranslateService, private translate: TranslateService,
protected store: Store<AppState>, protected store: Store<AppState>,
private raf: RafService) { private raf: RafService,
private cd: ChangeDetectorRef) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -128,6 +130,7 @@ export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Va
if (this.hasErrors !== hasErrors) { if (this.hasErrors !== hasErrors) {
this.hasErrors = hasErrors; this.hasErrors = hasErrors;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.cd.markForCheck();
} }
}); });
this.editorResize$ = new ResizeObserver(() => { this.editorResize$ = new ResizeObserver(() => {
@ -202,6 +205,7 @@ export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Va
if (this.modelValue !== editorValue) { if (this.modelValue !== editorValue) {
this.modelValue = editorValue; this.modelValue = editorValue;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.cd.markForCheck();
} }
} }
} }

41
ui-ngx/src/app/shared/components/html.component.html

@ -0,0 +1,41 @@
<!--
Copyright © 2016-2022 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-html" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight}"
tb-fullscreen
[fullscreen]="fullscreen" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-html-toolbar">
<label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors || required && !modelValue, 'tb-required': required}">{{ label }}</label>
<span fxFlex></span>
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyHtml()">
{{'js-func.tidy' | translate }}
</button>
<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-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-html-panel" class="tb-html-content-panel" fxLayout="column">
<div #htmlEditor id="tb-html-input" [ngClass]="{'fill-height': fillHeight}"></div>
</div>
</div>

65
ui-ngx/src/app/shared/components/html.component.scss

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2022 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-html {
position: relative;
&.tb-disabled {
color: rgba(0, 0, 0, .38);
}
&.fill-height {
height: 100%;
}
.tb-html-content-panel {
height: calc(100% - 40px);
border: 1px solid #c0c0c0;
#tb-html-input {
width: 100%;
min-width: 200px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
}
}
.tb-html-toolbar {
& > * {
&:not(:last-child) {
margin-right: 4px;
}
}
button.mat-button, button.mat-icon-button, button.mat-icon-button.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;
}
}
.tb-help-popup-button-loading {
background: #f3f3f3;
}
}
}

211
ui-ngx/src/app/shared/components/html.component.ts

@ -0,0 +1,211 @@
///
/// Copyright © 2016-2022 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, FormControl, 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 { beautifyHtml } from '@shared/models/beautify.models';
@Component({
selector: 'tb-html',
templateUrl: './html.component.html',
styleUrls: ['./html.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => HtmlComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => HtmlComponent),
multi: true,
}
],
encapsulation: ViewEncapsulation.None
})
export class HtmlComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
@ViewChild('htmlEditor', {static: true})
htmlEditorElmRef: ElementRef;
private htmlEditor: Ace.Editor;
private editorsResizeCaf: CancelAnimationFrame;
private editorResize$: ResizeObserver;
private ignoreChange = false;
@Input() label: string;
@Input() disabled: boolean;
@Input() fillHeight: boolean;
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.htmlEditorElmRef.nativeElement;
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/html',
showGutter: true,
showPrintMargin: true,
readOnly: this.disabled
};
const advancedOptions = {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
};
editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe(
(ace) => {
this.htmlEditor = ace.edit(editorElement, editorOptions);
this.htmlEditor.session.setUseWrapMode(true);
this.htmlEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.htmlEditor.setReadOnly(this.disabled);
this.htmlEditor.on('change', () => {
if (!this.ignoreChange) {
this.updateView();
}
});
// @ts-ignore
this.htmlEditor.session.on('changeAnnotation', () => {
const annotations = this.htmlEditor.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();
}
}
private onAceEditorResize() {
if (this.editorsResizeCaf) {
this.editorsResizeCaf();
this.editorsResizeCaf = null;
}
this.editorsResizeCaf = this.raf.raf(() => {
this.htmlEditor.resize();
this.htmlEditor.renderer.updateFull();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.htmlEditor) {
this.htmlEditor.setReadOnly(this.disabled);
}
}
public validate(c: FormControl) {
return (!this.hasErrors) ? null : {
html: {
valid: false,
},
};
}
beautifyHtml() {
beautifyHtml(this.modelValue, {indent_size: 4}).subscribe(
(res) => {
if (this.modelValue !== res) {
this.htmlEditor.setValue(res ? res : '', -1);
this.updateView();
}
}
);
}
writeValue(value: string): void {
this.modelValue = value;
if (this.htmlEditor) {
this.ignoreChange = true;
this.htmlEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.ignoreChange = false;
}
}
updateView() {
const editorValue = this.htmlEditor.getValue();
if (this.modelValue !== editorValue) {
this.modelValue = editorValue;
this.propagateChange(this.modelValue);
this.cd.markForCheck();
}
}
}

8
ui-ngx/src/app/shared/components/js-func.component.html

@ -19,8 +19,10 @@
tb-fullscreen tb-fullscreen
[fullscreen]="fullscreen" fxLayout="column"> [fullscreen]="fullscreen" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-js-func-toolbar"> <div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-js-func-toolbar">
<label *ngIf="functionTitle" class="tb-title no-padding">{{functionTitle + ': f(' + functionArgsString + ')' }}</label> <label *ngIf="functionTitle" class="tb-title no-padding"
<label *ngIf="!functionTitle" class="tb-title no-padding">{{'function ' + (functionName ? functionName : '') + '(' + functionArgsString + ') {'}}</label> [ngClass]="{'tb-error': hasErrors || !functionValid || required && !modelValue, 'tb-required': required}">{{functionTitle + ': f(' + functionArgsString + ')' }}</label>
<label *ngIf="!functionTitle" class="tb-title no-padding"
[ngClass]="{'tb-error': hasErrors || !functionValid || required && !modelValue, 'tb-required': required}">{{'function ' + (functionName ? functionName : '') + '(' + functionArgsString + ') {'}}</label>
<span fxFlex></span> <span fxFlex></span>
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyJs()"> <button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyJs()">
{{'js-func.tidy' | translate }} {{'js-func.tidy' | translate }}
@ -41,6 +43,6 @@
<div #javascriptEditor id="tb-javascript-input" [ngClass]="{'fill-height': fillHeight}"></div> <div #javascriptEditor id="tb-javascript-input" [ngClass]="{'fill-height': fillHeight}"></div>
</div> </div>
<div *ngIf="!functionTitle" fxLayout="row" fxLayoutAlign="start center" style="height: 40px;"> <div *ngIf="!functionTitle" fxLayout="row" fxLayoutAlign="start center" style="height: 40px;">
<label class="tb-title no-padding">}</label> <label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors || !functionValid || required && !modelValue}">}</label>
</div> </div>
</div> </div>

9
ui-ngx/src/app/shared/components/js-func.component.ts

@ -15,6 +15,7 @@
/// ///
import { import {
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
forwardRef, forwardRef,
@ -125,13 +126,14 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
errorAnnotationId = -1; errorAnnotationId = -1;
private propagateChange = null; private propagateChange = null;
private hasErrors = false; public hasErrors = false;
constructor(public elementRef: ElementRef, constructor(public elementRef: ElementRef,
private utils: UtilsService, private utils: UtilsService,
private translate: TranslateService, private translate: TranslateService,
protected store: Store<AppState>, protected store: Store<AppState>,
private raf: RafService) { private raf: RafService,
private cd: ChangeDetectorRef) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -185,6 +187,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
if (this.hasErrors !== hasErrors) { if (this.hasErrors !== hasErrors) {
this.hasErrors = hasErrors; this.hasErrors = hasErrors;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.cd.markForCheck();
} }
}); });
} }
@ -273,6 +276,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
this.functionValid = this.validateJsFunc(); this.functionValid = this.validateJsFunc();
if (!this.functionValid) { if (!this.functionValid) {
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.cd.markForCheck();
this.store.dispatch(new ActionNotificationShow( this.store.dispatch(new ActionNotificationShow(
{ {
message: this.validationError, message: this.validationError,
@ -400,6 +404,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
this.modelValue = editorValue; this.modelValue = editorValue;
this.functionValid = true; this.functionValid = true;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.cd.markForCheck();
} }
} }
} }

2
ui-ngx/src/app/shared/components/markdown-editor.component.html

@ -18,7 +18,7 @@
<div class="markdown-content" [ngClass]="{'tb-edit-mode': !readonly}" <div class="markdown-content" [ngClass]="{'tb-edit-mode': !readonly}"
tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()"> tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()">
<div *ngIf="label" fxLayout="row" fxLayoutAlign="start center" style="height: 40px;"> <div *ngIf="label" fxLayout="row" fxLayoutAlign="start center" style="height: 40px;">
<label class="tb-title no-padding" [ngClass]="{'tb-required': required}">{{ label }}</label> <label class="tb-title no-padding" [ngClass]="{'tb-error': required && !markdownValue, 'tb-required': required}">{{ label }}</label>
</div> </div>
<div [fxShow]="!readonly && !disabled" class="markdown-content-editor"> <div [fxShow]="!readonly && !disabled" class="markdown-content-editor">
<div class="buttons-panel"> <div class="buttons-panel">

3
ui-ngx/src/app/shared/shared.module.ts

@ -159,6 +159,7 @@ import { HELP_MARKDOWN_COMPONENT_TOKEN, SHARED_MODULE_TOKEN } from '@shared/comp
import { TbMarkdownComponent } from '@shared/components/markdown.component'; import { TbMarkdownComponent } from '@shared/components/markdown.component';
import { ProtobufContentComponent } from '@shared/components/protobuf-content.component'; import { ProtobufContentComponent } from '@shared/components/protobuf-content.component';
import { CssComponent } from '@shared/components/css.component'; import { CssComponent } from '@shared/components/css.component';
import { HtmlComponent } from '@shared/components/html.component';
import { SafePipe } from '@shared/pipe/safe.pipe'; import { SafePipe } from '@shared/pipe/safe.pipe';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
@ -240,6 +241,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
JsonContentComponent, JsonContentComponent,
JsFuncComponent, JsFuncComponent,
CssComponent, CssComponent,
HtmlComponent,
FabTriggerDirective, FabTriggerDirective,
FabActionsDirective, FabActionsDirective,
FabToolbarComponent, FabToolbarComponent,
@ -389,6 +391,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
JsonContentComponent, JsonContentComponent,
JsFuncComponent, JsFuncComponent,
CssComponent, CssComponent,
HtmlComponent,
FabTriggerDirective, FabTriggerDirective,
FabActionsDirective, FabActionsDirective,
FabToolbarComponent, FabToolbarComponent,

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

@ -3260,6 +3260,10 @@
"sort-settings": "Sort settings", "sort-settings": "Sort settings",
"nodes-sort-function": "Nodes sort function" "nodes-sort-function": "Nodes sort function"
}, },
"html-card": {
"html": "HTML",
"css": "CSS"
},
"input-widgets": { "input-widgets": {
"attribute-not-allowed": "Attribute parameter cannot be used in this widget", "attribute-not-allowed": "Attribute parameter cannot be used in this widget",
"blocked-location": "Geolocation is blocked in your browser", "blocked-location": "Geolocation is blocked in your browser",

Loading…
Cancel
Save