Browse Source

UI: Updated configuration rule chain note

pull/15121/head
Vladyslav_Prykhodko 7 months ago
parent
commit
bcf4dd0c89
  1. 6
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainNote.java
  2. 27
      ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html
  3. 9
      ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.scss
  4. 72
      ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts
  5. 15
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  6. 17
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  7. 7
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html
  8. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.scss
  9. 14
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.ts
  10. 6
      ui-ngx/src/app/shared/models/rule-node.models.ts
  11. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainNote.java

@ -49,6 +49,12 @@ public class RuleChainNote {
@Schema(description = "Background color of the note in CSS hex format, e.g. '#FFF9C4'")
private String backgroundColor;
@Schema(description = "Border color of the note in CSS hex format, e.g. '#E6C800'")
private String borderColor;
@Schema(description = "Border width of the note in pixels")
private Integer borderWidth;
@Schema(description = "Whether to apply the default markdown stylesheet to the note content")
private Boolean applyDefaultMarkdownStyle;

27
ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html

@ -15,23 +15,36 @@
limitations under the License.
-->
<form [formGroup]="noteForm" class="mat-padding flex flex-1 flex-col">
<form [formGroup]="noteForm" class="tb-form-panel no-border">
<tb-markdown-editor formControlName="content"
label="{{ 'rulechain.note-content' | translate }}"
helpId="rulechain/note_content"
[helpPopupStyle]="{'max-width': '850px'}">
</tb-markdown-editor>
<div class="tb-form-row space-between">
<div>{{ 'rulechain.note-background-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="backgroundColor">
</tb-color-input>
</div>
<section class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel #advancedPanel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-title translate>rule-node-config.advanced-settings</mat-panel-title>
</mat-expansion-panel-header>
<div class="tb-form-row space-between">
<div>{{ 'rulechain.note-background-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="backgroundColor">
</tb-color-input>
<div>{{ 'rulechain.note-border' | translate }}</div>
<div class="flex flex-row items-center gap-2">
<mat-form-field class="number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="borderWidth">
<span matSuffix>px</span>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="borderColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="applyDefaultMarkdownStyle">

9
ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.scss

@ -0,0 +1,9 @@
:host ::ng-deep {
tb-markdown-editor {
.markdown-content.tb-edit-mode {
&:not(.tb-fullscreen) {
padding-bottom: 0;
}
}
}
}

72
ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts

@ -14,44 +14,92 @@
/// limitations under the License.
///
import { Component, inject, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, DestroyRef, inject, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatExpansionPanel } from '@angular/material/expansion';
import { FormBuilder, Validators } from '@angular/forms';
import { FcRuleNote } from '@shared/models/rule-node.models';
import {
FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE as DEFAULT_APPLY_MARKDOWN_STYLE,
FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR as DEFAULT_BACKGROUND_COLOR,
FC_RULE_NOTE_DEFAULT_BORDER_WIDTH as DEFAULT_BORDER_WIDTH,
FcRuleNote
} from '@shared/models/rule-node.models';
import { isNotEmptyStr } from '@core/utils';
import tinycolor from 'tinycolor2';
@Component({
selector: 'tb-rule-note-editor',
templateUrl: './rule-note-editor.component.html',
standalone: false
selector: 'tb-rule-note-editor',
templateUrl: './rule-note-editor.component.html',
styleUrls: ['./rule-note-editor.component.scss'],
standalone: false
})
export class RuleNoteEditorComponent implements OnChanges {
export class RuleNoteEditorComponent implements OnChanges, OnInit {
@Input()
note: FcRuleNote;
private fb = inject(FormBuilder)
@ViewChild('advancedPanel') advancedPanel: MatExpansionPanel;
private fb = inject(FormBuilder);
private destroyRef = inject(DestroyRef);
noteForm = this.fb.group({
content: ['', Validators.maxLength(65536)],
backgroundColor: ['#FFF9C4'],
applyDefaultMarkdownStyle: [true],
backgroundColor: [DEFAULT_BACKGROUND_COLOR],
borderColor: [tinycolor(DEFAULT_BACKGROUND_COLOR).darken(20).toString()],
borderWidth: [DEFAULT_BORDER_WIDTH, [Validators.min(0)]],
applyDefaultMarkdownStyle: [DEFAULT_APPLY_MARKDOWN_STYLE],
markdownCss: ['', Validators.maxLength(65536)]
});
ngOnInit(): void {
this.noteForm.get('backgroundColor').valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(color => {
const borderControl = this.noteForm.get('borderColor');
if (borderControl.pristine) {
borderControl.setValue(this.borderColorFrom(color), { emitEvent: false });
}
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.note) {
this.updatedForm();
}
}
private borderColorFrom(bg: string): string {
return tinycolor(bg || DEFAULT_BACKGROUND_COLOR).darken(20).toString();
}
private updatedForm(): void {
const bg = this.note?.backgroundColor || DEFAULT_BACKGROUND_COLOR;
this.noteForm.setValue({
content: this.note?.content || '',
backgroundColor: this.note?.backgroundColor || '#FFF9C4',
applyDefaultMarkdownStyle: this.note?.applyDefaultMarkdownStyle ?? true,
backgroundColor: bg,
borderColor: this.note?.borderColor || this.borderColorFrom(bg),
borderWidth: this.note?.borderWidth ?? DEFAULT_BORDER_WIDTH,
applyDefaultMarkdownStyle: this.note?.applyDefaultMarkdownStyle ?? DEFAULT_APPLY_MARKDOWN_STYLE,
markdownCss: this.note?.markdownCss || ''
})
});
const shouldExpand = this.hasNonDefaultAdvancedSettings(bg);
setTimeout(() => {
if (shouldExpand) {
this.advancedPanel?.open();
} else {
this.advancedPanel?.close();
}
this.noteForm.markAsPristine();
}, 0);
}
private hasNonDefaultAdvancedSettings(bg: string): boolean {
const note = this.note;
if (!note) return false;
return (note.borderColor != null && note.borderColor !== this.borderColorFrom(bg))
|| (note.borderWidth != null && note.borderWidth !== DEFAULT_BORDER_WIDTH)
|| note.applyDefaultMarkdownStyle === false
|| isNotEmptyStr(note.markdownCss);
}
}

15
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss

@ -193,14 +193,15 @@
border: solid 2px rgba(0, 0, 0, 0.35);
}
}
}
fc-note {
&.fc-selected:not(.fc-edit) {
box-shadow: 0 0 0 3px #f00;
}
&.fc-selected.fc-edit {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.35);
}
.fc-note {
border-radius: 8px;
&.fc-selected:not(.fc-edit) {
box-shadow: 0 0 0 3px #f00;
}
&.fc-selected.fc-edit {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.35);
}
}

17
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -170,7 +170,7 @@ export class RuleChainPageComponent extends PageComponent
editingNote: FcRuleNote = null;
isEditingNote = false;
editingNoteId: string = null;
editingNoteIndex = -1;
hotKeys: Hotkey[] = [];
@ -682,7 +682,7 @@ export class RuleChainPageComponent extends PageComponent
}
});
}
this.ruleChainModel.notes = this.ruleChainMetaData.notes || [];
this.ruleChainModel.notes = deepClone(this.ruleChainMetaData.notes) || [];
if (this.ruleChainCanvas) {
this.ruleChainCanvas.adjustCanvasSize(true);
}
@ -1365,7 +1365,7 @@ export class RuleChainPageComponent extends PageComponent
icon: 'sticky_note_2',
title: this.translate.instant('rulechain.note'),
subtitle: (() => {
const plain = (note.content || '').replace(/[#*_`>\[\]!\\~]/g, '').trim();
const plain = (note.content || '').replace(/[#*_`>[\]!\\~]/g, '').trim();
return plain.length > 24 ? plain.substring(0, 24) + '…' : plain;
})(),
menuItems: []
@ -1414,30 +1414,27 @@ export class RuleChainPageComponent extends PageComponent
this.isEditingRuleNodeLink = false;
this.editingRuleNodeLink = null;
this.isEditingNote = true;
this.editingNoteId = note.id;
this.editingNoteIndex = this.ruleChainModel.notes.indexOf(note);
this.editingNote = deepClone(note);
}
saveNote(): void {
this.ruleNoteComponent.noteForm.markAsPristine();
Object.assign(this.editingNote, this.ruleNoteComponent.noteForm.value);
const idx = this.ruleChainModel.notes.findIndex(n => n.id === this.editingNoteId);
this.ruleChainModel.notes[idx] = this.editingNote;
this.ruleChainModel.notes[this.editingNoteIndex] = this.editingNote;
this.editingNote = deepClone(this.editingNote);
this.isDirty = true;
this.onModelChanged();
}
onRevertNoteEdit(): void {
this.ruleNoteComponent.noteForm.markAsPristine();
const note = this.ruleChainModel.notes.find(n => n.id === this.editingNoteId);
const note = this.ruleChainModel.notes[this.editingNoteIndex];
this.editingNote = deepClone(note);
}
onEditNoteClosed(): void {
this.editingNote = null;
this.isEditingNote = false;
this.editingNoteId = null;
}
onDetailsDrawerClosed() {
@ -1721,7 +1718,7 @@ export class RuleChainPageComponent extends PageComponent
this.ruleChain.version = savedRuleChainMetaData.version;
this.ruleChainMetaData = savedRuleChainMetaData;
if (!this.ruleChainMetaData.notes) {
this.ruleChainMetaData.notes = this.ruleChainModel.notes || [];
this.ruleChainMetaData.notes = deepClone(this.ruleChainModel.notes) || [];
}
if (this.isImport) {
this.isDirtyValue = false;

7
ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html

@ -28,12 +28,13 @@
</div>
}
<div class="tb-rule-note-bg"
[style.background-color]="note.backgroundColor || '#FFF9C4'"
[style.border-color]="borderColorFor(note.backgroundColor)"
[style.background-color]="note.backgroundColor || defaultBackgroundColor"
[style.border-color]="note.borderColor"
[style.border-width]="note.borderWidth !== null ? note.borderWidth + 'px' : null"
(dblclick)="userNoteCallbacks?.doubleClick?.($event, note)">
@if (note?.content) {
<tb-markdown [data]="note.content"
[applyDefaultMarkdownStyle]="note.applyDefaultMarkdownStyle ?? true"
[applyDefaultMarkdownStyle]="note.applyDefaultMarkdownStyle ?? defaultApplyMarkdownStyle"
[additionalStyles]="additionalStyles"
[containerClass]="noteClass"
fallbackToPlainMarkdown

2
ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.scss

@ -57,7 +57,7 @@
height: 100%;
background-color: #FFF9C4;
border: 1px solid #E6D600;
border-radius: 4px;
border-radius: 8px;
box-sizing: border-box;
padding: 8px;
overflow: auto;

14
ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.ts

@ -18,8 +18,11 @@ import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { FcNoteComponent } from 'ngx-flowchart';
import cssjs from '@core/css/css';
import { hashCode, isNotEmptyStr } from '@core/utils';
import tinycolor from 'tinycolor2';
import { FcRuleNote } from '@shared/models/rule-node.models';
import {
FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE,
FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR,
FcRuleNote
} from '@shared/models/rule-node.models';
@Component({
selector: 'tb-rule-note',
@ -29,6 +32,9 @@ import { FcRuleNote } from '@shared/models/rule-node.models';
})
export class RuleNoteComponent extends FcNoteComponent implements OnInit, OnChanges {
readonly defaultBackgroundColor = FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR;
readonly defaultApplyMarkdownStyle = FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE;
additionalStyles: string[];
noteClass: string;
note: FcRuleNote;
@ -44,10 +50,6 @@ export class RuleNoteComponent extends FcNoteComponent implements OnInit, OnChan
}
}
borderColorFor(backgroundColor: string): string {
return tinycolor(backgroundColor || '#FFF9C4').darken(20).toString();
}
private processCss(): void {
let cssString = this.note?.markdownCss;
if (isNotEmptyStr(cssString)) {

6
ui-ngx/src/app/shared/models/rule-node.models.ts

@ -359,9 +359,15 @@ export interface FcRuleEdge extends FcEdge {
labels?: string[];
}
export const FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR = '#FFF9C4';
export const FC_RULE_NOTE_DEFAULT_BORDER_WIDTH = 1;
export const FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE = true;
export interface FcRuleNote extends FcNote {
content?: string;
backgroundColor?: string;
borderColor?: string;
borderWidth?: number;
applyDefaultMarkdownStyle?: boolean;
markdownCss?: string;
}

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

@ -5174,6 +5174,7 @@
"note-details": "Note details",
"note-empty": "Empty note",
"note-background-color": "Background color",
"note-border": "Border",
"note-apply-default-markdown-style": "Apply default markdown style",
"note-custom-css": "Note content CSS"
},

Loading…
Cancel
Save