From bcf4dd0c89ed15f9eac10a69ba2fa9a9e15664d1 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 2 Mar 2026 16:16:27 +0200 Subject: [PATCH] UI: Updated configuration rule chain note --- .../common/data/rule/RuleChainNote.java | 6 ++ .../rulechain/rule-note-editor.component.html | 27 +++++-- .../rulechain/rule-note-editor.component.scss | 9 +++ .../rulechain/rule-note-editor.component.ts | 72 +++++++++++++++---- .../rulechain/rulechain-page.component.scss | 15 ++-- .../rulechain/rulechain-page.component.ts | 17 ++--- .../pages/rulechain/rulenote.component.html | 7 +- .../pages/rulechain/rulenote.component.scss | 2 +- .../pages/rulechain/rulenote.component.ts | 14 ++-- .../src/app/shared/models/rule-node.models.ts | 6 ++ .../assets/locale/locale.constant-en_US.json | 1 + 11 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.scss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainNote.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainNote.java index 96715a6700..91e2926ac8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainNote.java +++ b/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; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html index dcaea7550f..015c2a38d4 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.html @@ -15,23 +15,36 @@ limitations under the License. --> -
+ +
+
{{ 'rulechain.note-background-color' | translate }}
+ + +
- + rule-node-config.advanced-settings
-
{{ 'rulechain.note-background-color' | translate }}
- - +
{{ 'rulechain.note-border' | translate }}
+
+ + + px + + + +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.scss new file mode 100644 index 0000000000..033e82b0b1 --- /dev/null +++ b/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; + } + } + } +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts index c0b94fd8d7..284d44f9dc 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts +++ b/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); + } } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss index 1bdbae1280..0236e2f85d 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss +++ b/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); } } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts index ee0f1fd72f..60217a04d9 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts +++ b/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; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html index f0db4f82d6..a14b30dcb0 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html @@ -28,12 +28,13 @@
}
@if (note?.content) {