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'") @Schema(description = "Background color of the note in CSS hex format, e.g. '#FFF9C4'")
private String backgroundColor; 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") @Schema(description = "Whether to apply the default markdown stylesheet to the note content")
private Boolean applyDefaultMarkdownStyle; 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. 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" <tb-markdown-editor formControlName="content"
label="{{ 'rulechain.note-content' | translate }}" label="{{ 'rulechain.note-content' | translate }}"
helpId="rulechain/note_content" helpId="rulechain/note_content"
[helpPopupStyle]="{'max-width': '850px'}"> [helpPopupStyle]="{'max-width': '850px'}">
</tb-markdown-editor> </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"> <section class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings"> <mat-expansion-panel #advancedPanel class="tb-settings">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title translate>rule-node-config.advanced-settings</mat-panel-title> <mat-panel-title translate>rule-node-config.advanced-settings</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'rulechain.note-background-color' | translate }}</div> <div>{{ 'rulechain.note-border' | translate }}</div>
<tb-color-input asBoxInput <div class="flex flex-row items-center gap-2">
colorClearButton <mat-form-field class="number" appearance="outline" subscriptSizing="dynamic">
formControlName="backgroundColor"> <input matInput type="number" min="0" formControlName="borderWidth">
</tb-color-input> <span matSuffix>px</span>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="borderColor">
</tb-color-input>
</div>
</div> </div>
<div class="tb-form-row"> <div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="applyDefaultMarkdownStyle"> <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. /// 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 { 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({ @Component({
selector: 'tb-rule-note-editor', selector: 'tb-rule-note-editor',
templateUrl: './rule-note-editor.component.html', templateUrl: './rule-note-editor.component.html',
standalone: false styleUrls: ['./rule-note-editor.component.scss'],
standalone: false
}) })
export class RuleNoteEditorComponent implements OnChanges { export class RuleNoteEditorComponent implements OnChanges, OnInit {
@Input() @Input()
note: FcRuleNote; note: FcRuleNote;
private fb = inject(FormBuilder) @ViewChild('advancedPanel') advancedPanel: MatExpansionPanel;
private fb = inject(FormBuilder);
private destroyRef = inject(DestroyRef);
noteForm = this.fb.group({ noteForm = this.fb.group({
content: ['', Validators.maxLength(65536)], content: ['', Validators.maxLength(65536)],
backgroundColor: ['#FFF9C4'], backgroundColor: [DEFAULT_BACKGROUND_COLOR],
applyDefaultMarkdownStyle: [true], borderColor: [tinycolor(DEFAULT_BACKGROUND_COLOR).darken(20).toString()],
borderWidth: [DEFAULT_BORDER_WIDTH, [Validators.min(0)]],
applyDefaultMarkdownStyle: [DEFAULT_APPLY_MARKDOWN_STYLE],
markdownCss: ['', Validators.maxLength(65536)] 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 { ngOnChanges(changes: SimpleChanges): void {
if (changes.note) { if (changes.note) {
this.updatedForm(); this.updatedForm();
} }
} }
private borderColorFrom(bg: string): string {
return tinycolor(bg || DEFAULT_BACKGROUND_COLOR).darken(20).toString();
}
private updatedForm(): void { private updatedForm(): void {
const bg = this.note?.backgroundColor || DEFAULT_BACKGROUND_COLOR;
this.noteForm.setValue({ this.noteForm.setValue({
content: this.note?.content || '', content: this.note?.content || '',
backgroundColor: this.note?.backgroundColor || '#FFF9C4', backgroundColor: bg,
applyDefaultMarkdownStyle: this.note?.applyDefaultMarkdownStyle ?? true, 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 || '' markdownCss: this.note?.markdownCss || ''
}) });
const shouldExpand = this.hasNonDefaultAdvancedSettings(bg);
setTimeout(() => { setTimeout(() => {
if (shouldExpand) {
this.advancedPanel?.open();
} else {
this.advancedPanel?.close();
}
this.noteForm.markAsPristine(); this.noteForm.markAsPristine();
}, 0); }, 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); border: solid 2px rgba(0, 0, 0, 0.35);
} }
} }
}
fc-note { .fc-note {
&.fc-selected:not(.fc-edit) { border-radius: 8px;
box-shadow: 0 0 0 3px #f00; &.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-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; editingNote: FcRuleNote = null;
isEditingNote = false; isEditingNote = false;
editingNoteId: string = null; editingNoteIndex = -1;
hotKeys: Hotkey[] = []; 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) { if (this.ruleChainCanvas) {
this.ruleChainCanvas.adjustCanvasSize(true); this.ruleChainCanvas.adjustCanvasSize(true);
} }
@ -1365,7 +1365,7 @@ export class RuleChainPageComponent extends PageComponent
icon: 'sticky_note_2', icon: 'sticky_note_2',
title: this.translate.instant('rulechain.note'), title: this.translate.instant('rulechain.note'),
subtitle: (() => { subtitle: (() => {
const plain = (note.content || '').replace(/[#*_`>\[\]!\\~]/g, '').trim(); const plain = (note.content || '').replace(/[#*_`>[\]!\\~]/g, '').trim();
return plain.length > 24 ? plain.substring(0, 24) + '…' : plain; return plain.length > 24 ? plain.substring(0, 24) + '…' : plain;
})(), })(),
menuItems: [] menuItems: []
@ -1414,30 +1414,27 @@ export class RuleChainPageComponent extends PageComponent
this.isEditingRuleNodeLink = false; this.isEditingRuleNodeLink = false;
this.editingRuleNodeLink = null; this.editingRuleNodeLink = null;
this.isEditingNote = true; this.isEditingNote = true;
this.editingNoteId = note.id; this.editingNoteIndex = this.ruleChainModel.notes.indexOf(note);
this.editingNote = deepClone(note); this.editingNote = deepClone(note);
} }
saveNote(): void { saveNote(): void {
this.ruleNoteComponent.noteForm.markAsPristine(); this.ruleNoteComponent.noteForm.markAsPristine();
Object.assign(this.editingNote, this.ruleNoteComponent.noteForm.value); Object.assign(this.editingNote, this.ruleNoteComponent.noteForm.value);
const idx = this.ruleChainModel.notes.findIndex(n => n.id === this.editingNoteId); this.ruleChainModel.notes[this.editingNoteIndex] = this.editingNote;
this.ruleChainModel.notes[idx] = this.editingNote;
this.editingNote = deepClone(this.editingNote); this.editingNote = deepClone(this.editingNote);
this.isDirty = true;
this.onModelChanged(); this.onModelChanged();
} }
onRevertNoteEdit(): void { onRevertNoteEdit(): void {
this.ruleNoteComponent.noteForm.markAsPristine(); 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); this.editingNote = deepClone(note);
} }
onEditNoteClosed(): void { onEditNoteClosed(): void {
this.editingNote = null; this.editingNote = null;
this.isEditingNote = false; this.isEditingNote = false;
this.editingNoteId = null;
} }
onDetailsDrawerClosed() { onDetailsDrawerClosed() {
@ -1721,7 +1718,7 @@ export class RuleChainPageComponent extends PageComponent
this.ruleChain.version = savedRuleChainMetaData.version; this.ruleChain.version = savedRuleChainMetaData.version;
this.ruleChainMetaData = savedRuleChainMetaData; this.ruleChainMetaData = savedRuleChainMetaData;
if (!this.ruleChainMetaData.notes) { if (!this.ruleChainMetaData.notes) {
this.ruleChainMetaData.notes = this.ruleChainModel.notes || []; this.ruleChainMetaData.notes = deepClone(this.ruleChainModel.notes) || [];
} }
if (this.isImport) { if (this.isImport) {
this.isDirtyValue = false; this.isDirtyValue = false;

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

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

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

@ -57,7 +57,7 @@
height: 100%; height: 100%;
background-color: #FFF9C4; background-color: #FFF9C4;
border: 1px solid #E6D600; border: 1px solid #E6D600;
border-radius: 4px; border-radius: 8px;
box-sizing: border-box; box-sizing: border-box;
padding: 8px; padding: 8px;
overflow: auto; 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 { FcNoteComponent } from 'ngx-flowchart';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { hashCode, isNotEmptyStr } from '@core/utils'; import { hashCode, isNotEmptyStr } from '@core/utils';
import tinycolor from 'tinycolor2'; import {
import { FcRuleNote } from '@shared/models/rule-node.models'; FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE,
FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR,
FcRuleNote
} from '@shared/models/rule-node.models';
@Component({ @Component({
selector: 'tb-rule-note', selector: 'tb-rule-note',
@ -29,6 +32,9 @@ import { FcRuleNote } from '@shared/models/rule-node.models';
}) })
export class RuleNoteComponent extends FcNoteComponent implements OnInit, OnChanges { 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[]; additionalStyles: string[];
noteClass: string; noteClass: string;
note: FcRuleNote; 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 { private processCss(): void {
let cssString = this.note?.markdownCss; let cssString = this.note?.markdownCss;
if (isNotEmptyStr(cssString)) { 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[]; 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 { export interface FcRuleNote extends FcNote {
content?: string; content?: string;
backgroundColor?: string; backgroundColor?: string;
borderColor?: string;
borderWidth?: number;
applyDefaultMarkdownStyle?: boolean; applyDefaultMarkdownStyle?: boolean;
markdownCss?: string; markdownCss?: string;
} }

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

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

Loading…
Cancel
Save