Browse Source

UI: Refactor rule chain note and fixed minor bugs

pull/15121/head
Vladyslav_Prykhodko 6 months ago
parent
commit
496bc44d9c
  1. 2
      ui-ngx/package.json
  2. 18
      ui-ngx/src/app/core/services/item-buffer.service.ts
  3. 2
      ui-ngx/src/app/modules/home/pages/rulechain/add-note-dialog.component.html
  4. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rule-note-editor.component.ts
  5. 37
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html
  6. 9
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  7. 38
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  8. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts
  9. 6
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.html
  10. 6
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.scss
  11. 20
      ui-ngx/src/app/modules/home/pages/rulechain/rulenote.component.ts
  12. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  13. 5
      ui-ngx/yarn.lock

2
ui-ngx/package.json

@ -71,7 +71,7 @@
"ngx-clipboard": "^16.0.0", "ngx-clipboard": "^16.0.0",
"ngx-daterangepicker-material": "^6.0.4", "ngx-daterangepicker-material": "^6.0.4",
"ngx-drag-drop": "^20.0.1", "ngx-drag-drop": "^20.0.1",
"ngx-flowchart": "file:../../ngx-flowchart/dist/ngx-flowchart", "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/4.1.0",
"ngx-hm-carousel": "^19.0.0", "ngx-hm-carousel": "^19.0.0",
"ngx-markdown": "^20.1.0", "ngx-markdown": "^20.1.0",
"ngx-sharebuttons": "^17.0.0", "ngx-sharebuttons": "^17.0.0",

18
ui-ngx/src/app/core/services/item-buffer.service.ts

@ -16,7 +16,13 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BreakpointId, Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models'; import { BreakpointId, Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models';
import { AliasesInfo, EntityAlias, EntityAliases, EntityAliasInfo, getEntityAliasId } from '@shared/models/alias.models'; import {
AliasesInfo,
EntityAlias,
EntityAliases,
EntityAliasInfo,
getEntityAliasId
} from '@shared/models/alias.models';
import { import {
Datasource, Datasource,
DatasourceType, DatasourceType,
@ -75,6 +81,8 @@ export interface RuleNodesReference {
notes?: FcRuleNote[]; notes?: FcRuleNote[];
originX?: number; originX?: number;
originY?: number; originY?: number;
minX?: number;
minY?: number;
} }
@Injectable({ @Injectable({
@ -375,6 +383,8 @@ export class ItemBufferService {
height: origNote.height, height: origNote.height,
content: origNote.content, content: origNote.content,
backgroundColor: origNote.backgroundColor, backgroundColor: origNote.backgroundColor,
borderWidth: origNote.borderWidth,
borderColor: origNote.borderColor,
applyDefaultMarkdownStyle: origNote.applyDefaultMarkdownStyle, applyDefaultMarkdownStyle: origNote.applyDefaultMarkdownStyle,
markdownCss: origNote.markdownCss markdownCss: origNote.markdownCss
}); });
@ -392,6 +402,8 @@ export class ItemBufferService {
} }
ruleNodes.originX = left + (right - left) / 2; ruleNodes.originX = left + (right - left) / 2;
ruleNodes.originY = top + (bottom - top) / 2; ruleNodes.originY = top + (bottom - top) / 2;
ruleNodes.minX = left;
ruleNodes.minY = top;
connections.forEach(connection => { connections.forEach(connection => {
ruleNodes.connections.push(connection); ruleNodes.connections.push(connection);
}); });
@ -405,8 +417,8 @@ export class ItemBufferService {
public pasteRuleChainObjects(x: number, y: number): RuleNodesReference { public pasteRuleChainObjects(x: number, y: number): RuleNodesReference {
const ruleNodes: RuleNodesReference = this.storeGet(RULE_NODES); const ruleNodes: RuleNodesReference = this.storeGet(RULE_NODES);
if (ruleNodes) { if (ruleNodes) {
const deltaX = x - ruleNodes.originX; const deltaX = isDefinedAndNotNull(ruleNodes.minX) ? Math.max(x - ruleNodes.originX, -ruleNodes.minX) : x - ruleNodes.originX;
const deltaY = y - ruleNodes.originY; const deltaY = isDefinedAndNotNull(ruleNodes.minY) ? Math.max(y - ruleNodes.originY, -ruleNodes.minY) : y - ruleNodes.originY;
for (const node of ruleNodes.nodes) { for (const node of ruleNodes.nodes) {
const component = this.ruleChainService.getRuleNodeComponentByClazz(node.ruleChainType, node.componentClazz); const component = this.ruleChainService.getRuleNodeComponentByClazz(node.ruleChainType, node.componentClazz);
if (component) { if (component) {

2
ui-ngx/src/app/modules/home/pages/rulechain/add-note-dialog.component.html

@ -27,7 +27,7 @@
</tb-rule-note-editor> </tb-rule-note-editor>
</div> </div>
<div mat-dialog-actions class="flex items-center justify-end"> <div mat-dialog-actions class="flex items-center justify-end">
<button mat-button color="primary" type="button" (click)="cancel()"> <button mat-button color="primary" type="button" (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary" type="button" (click)="save()"> <button mat-raised-button color="primary" type="button" (click)="save()">

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

@ -70,7 +70,8 @@ export class RuleNoteEditorComponent implements OnChanges, OnInit {
} }
private borderColorFrom(bg: string): string { private borderColorFrom(bg: string): string {
return tinycolor(bg || DEFAULT_BACKGROUND_COLOR).darken(20).toString(); const color = tinycolor(bg || DEFAULT_BACKGROUND_COLOR);
return color.isDark() ? color.lighten(20).toString() : color.darken(20).toString();
} }
private updatedForm(): void { private updatedForm(): void {

37
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html

@ -169,7 +169,7 @@
[theForm]="tbRuleNote.noteForm"> [theForm]="tbRuleNote.noteForm">
<tb-rule-note-editor #tbRuleNote <tb-rule-note-editor #tbRuleNote
[note]="editingNote" [note]="editingNote"
class="flex-1"> class="mt-3 flex-1">
</tb-rule-note-editor> </tb-rule-note-editor>
</tb-details-panel> </tb-details-panel>
</mat-drawer> </mat-drawer>
@ -182,16 +182,25 @@
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon class="tb-library-node-btn-icon" [class.tb-library-node-btn-icon-toggled]="drawer.opened">chevron_right</mat-icon> <mat-icon class="tb-library-node-btn-icon" [class.tb-library-node-btn-icon-toggled]="drawer.opened">chevron_right</mat-icon>
</button> </button>
<button #versionControlButton @if (!isImport) {
*ngIf="!isImport" <button color="primary"
color="primary" type="button"
type="button" mat-mini-fab class="add-note-button"
mat-mini-fab class="version-control-button" (click)="addNote()"
(click)="toggleVersionControl($event, versionControlButton)" matTooltip="{{'rulechain.add-note' | translate}}"
matTooltip="{{'version-control.version-control' | translate}}" matTooltipPosition="above">
matTooltipPosition="above"> <mat-icon>sticky_note_2</mat-icon>
<mat-icon>history</mat-icon> </button>
</button> <button #versionControlButton
color="primary"
type="button"
mat-mini-fab class="version-control-button"
(click)="toggleVersionControl($event, versionControlButton)"
matTooltip="{{'version-control.version-control' | translate}}"
matTooltipPosition="above">
<mat-icon>history</mat-icon>
</button>
}
<button type="button" <button type="button"
mat-icon-button class="tb-fullscreen-button tb-mat-40" mat-icon-button class="tb-fullscreen-button tb-mat-40"
(click)="isFullscreen = !isFullscreen" (click)="isFullscreen = !isFullscreen"
@ -212,9 +221,11 @@
<div class="tb-context-menu-header {{contextInfo.headerClass}}"> <div class="tb-context-menu-header {{contextInfo.headerClass}}">
<mat-icon *ngIf="!contextInfo.iconUrl">{{contextInfo.icon}}</mat-icon> <mat-icon *ngIf="!contextInfo.iconUrl">{{contextInfo.icon}}</mat-icon>
<img *ngIf="contextInfo.iconUrl" [src]="contextInfo.iconUrl"/> <img *ngIf="contextInfo.iconUrl" [src]="contextInfo.iconUrl"/>
<div class="flex-1"> <div class="flex-1 self-center">
<div class="tb-context-menu-title">{{contextInfo.title}}</div> <div class="tb-context-menu-title">{{contextInfo.title}}</div>
<div class="tb-context-menu-subtitle">{{contextInfo.subtitle}}</div> @if (contextInfo.subtitle) {
<div class="tb-context-menu-subtitle">{{contextInfo.subtitle}}</div>
}
</div> </div>
</div> </div>
<div *ngFor="let menuItem of contextInfo.menuItems"> <div *ngFor="let menuItem of contextInfo.menuItems">

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

@ -54,6 +54,15 @@
z-index: 2; z-index: 2;
} }
button.mdc-fab.add-note-button {
position: absolute;
top: 10px;
right: 110px;
opacity: .85;
margin: 0 6px;
z-index: 2;
}
.tb-library-node-btn { .tb-library-node-btn {
width: 20px; width: 20px;
height: 90px; height: 90px;

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

@ -62,6 +62,8 @@ import {
} from '@shared/models/rule-chain.models'; } from '@shared/models/rule-chain.models';
import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart'; import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart';
import { import {
FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE,
FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR,
FcRuleEdge, FcRuleEdge,
FcRuleNode, FcRuleNode,
FcRuleNodeType, FcRuleNodeType,
@ -377,12 +379,15 @@ export class RuleChainPageComponent extends PageComponent
this.selectedObjects = []; this.selectedObjects = [];
this.ruleChainModel.nodes = []; this.ruleChainModel.nodes = [];
this.ruleChainModel.edges = []; this.ruleChainModel.edges = [];
this.ruleChainModel.notes = [];
this.ruleNodeTypesModel = {}; this.ruleNodeTypesModel = {};
if (this.ruleChainCanvas) { if (this.ruleChainCanvas) {
this.ruleChainCanvas.adjustCanvasSize(true); this.ruleChainCanvas.adjustCanvasSize(true);
} }
this.isEditingRuleNode = false; this.isEditingRuleNode = false;
this.isEditingRuleNodeLink = false; this.isEditingRuleNodeLink = false;
this.isEditingNote = false;
this.editingNote = null;
this.updateRuleNodesHighlight(); this.updateRuleNodesHighlight();
} }
@ -733,7 +738,8 @@ export class RuleChainPageComponent extends PageComponent
subtitle: this.translate.instant('rulechain.rulechain'), subtitle: this.translate.instant('rulechain.rulechain'),
menuItems: [] menuItems: []
}; };
if (this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length) { if (this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length ||
this.ruleChainCanvas.modelService.notes.getSelectedNotes().length) {
contextInfo.menuItems.push( contextInfo.menuItems.push(
{ {
action: () => { action: () => {
@ -964,15 +970,13 @@ export class RuleChainPageComponent extends PageComponent
} }
}).afterClosed().subscribe((ruleChain) => { }).afterClosed().subscribe((ruleChain) => {
if (ruleChain) { if (ruleChain) {
const selectedNotes: FcRuleNote[] = this.ruleChainCanvas.modelService.notes.getSelectedNotes();
this.ruleChainCanvas.modelService.deselectAll(); this.ruleChainCanvas.modelService.deselectAll();
const selectedNotes: FcRuleNote[] = (this.ruleChainModel.notes || []).filter(
note => this.ruleChainCanvas.modelService.notes.isSelected(note)
);
const ruleChainMetaData: RuleChainMetaData = { const ruleChainMetaData: RuleChainMetaData = {
ruleChainId: ruleChain.id, ruleChainId: ruleChain.id,
nodes: [], nodes: [],
connections: [], connections: [],
notes: selectedNotes.map(note => ({ ...note })) notes: deepClone(selectedNotes)
}; };
let outputEdges: FcRuleEdge[] = []; let outputEdges: FcRuleEdge[] = [];
let minX: number = null; let minX: number = null;
@ -1137,6 +1141,9 @@ export class RuleChainPageComponent extends PageComponent
selectedNodes.forEach((node) => { selectedNodes.forEach((node) => {
this.ruleChainCanvas.modelService.nodes.delete(node); this.ruleChainCanvas.modelService.nodes.delete(node);
}); });
selectedNotes.forEach((note) => {
this.ruleChainCanvas.modelService.notes.delete(note);
});
this.onModelChanged(); this.onModelChanged();
this.updateRuleNodesHighlight(); this.updateRuleNodesHighlight();
}); });
@ -1317,15 +1324,15 @@ export class RuleChainPageComponent extends PageComponent
} }
} }
private addNote(event?: MouseEvent): void { addNote(event?: MouseEvent): void {
this.dialog.open<AddNoteDialogComponent, FcRuleNote, Partial<FcRuleNote>>(AddNoteDialogComponent, { this.dialog.open<AddNoteDialogComponent, FcRuleNote, Partial<FcRuleNote>>(AddNoteDialogComponent, {
disableClose: false, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
id: '', x: 0, y: 0, width: 200, height: 120, id: '', x: 0, y: 0, width: 200, height: 120,
content: '', content: '',
backgroundColor: '#FFF9C4', backgroundColor: FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR,
applyDefaultMarkdownStyle: true, applyDefaultMarkdownStyle: FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE,
markdownCss: '' markdownCss: ''
} }
}).afterClosed().subscribe((noteData) => { }).afterClosed().subscribe((noteData) => {
@ -1364,10 +1371,6 @@ export class RuleChainPageComponent extends PageComponent
headerClass: 'tb-rulechain-header', headerClass: 'tb-rulechain-header',
icon: 'sticky_note_2', icon: 'sticky_note_2',
title: this.translate.instant('rulechain.note'), title: this.translate.instant('rulechain.note'),
subtitle: (() => {
const plain = (note.content || '').replace(/[#*_`>[\]!\\~]/g, '').trim();
return plain.length > 24 ? plain.substring(0, 24) + '…' : plain;
})(),
menuItems: [] menuItems: []
}; };
if (!note.readonly) { if (!note.readonly) {
@ -1600,7 +1603,8 @@ export class RuleChainPageComponent extends PageComponent
objectsSelected(): boolean { objectsSelected(): boolean {
return this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length > 0 || return this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length > 0 ||
this.ruleChainCanvas.modelService.edges.getSelectedEdges().length > 0; this.ruleChainCanvas.modelService.edges.getSelectedEdges().length > 0 ||
this.ruleChainCanvas.modelService.notes.getSelectedNotes().length > 0;
} }
deleteSelected() { deleteSelected() {
@ -1658,7 +1662,7 @@ export class RuleChainPageComponent extends PageComponent
ruleChainId: this.ruleChain.id, ruleChainId: this.ruleChain.id,
nodes: [], nodes: [],
connections: [], connections: [],
notes: this.ruleChainModel.notes || [], notes: deepClone(this.ruleChainModel.notes) || [],
version: ruleChain.version version: ruleChain.version
}; };
const nodes: FcRuleNode[] = []; const nodes: FcRuleNode[] = [];
@ -2181,6 +2185,8 @@ export class AddNoteDialogComponent extends DialogComponent<AddNoteDialogCompone
} }
save(): void { save(): void {
this.dialogRef.close(this.ruleNoteComponent.noteForm.value); if (this.ruleNoteComponent.noteForm.valid) {
this.dialogRef.close(this.ruleNoteComponent.noteForm.value);
}
} }
} }

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts

@ -40,6 +40,6 @@ export interface RuleChainMenuContextInfo {
icon: string; icon: string;
iconUrl?: string; iconUrl?: string;
title: string; title: string;
subtitle: string; subtitle?: string;
menuItems: RuleChainMenuItem[]; menuItems: RuleChainMenuItem[];
} }

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

@ -34,15 +34,11 @@
(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 ?? defaultApplyMarkdownStyle" [applyDefaultMarkdownStyle]="applyDefault"
[additionalStyles]="additionalStyles" [additionalStyles]="additionalStyles"
[containerClass]="noteClass" [containerClass]="noteClass"
fallbackToPlainMarkdown fallbackToPlainMarkdown
usePlainMarkdown> usePlainMarkdown>
</tb-markdown> </tb-markdown>
} @else {
<span class="tb-rule-note-placeholder">
{{ 'rulechain.note-empty' | translate }}
</span>
} }
</div> </div>

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

@ -62,10 +62,4 @@
padding: 8px; padding: 8px;
overflow: auto; overflow: auto;
} }
.tb-rule-note-placeholder {
color: rgba(0, 0, 0, 0.38);
font-style: italic;
font-size: 13px;
}
} }

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

@ -33,25 +33,35 @@ import {
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 defaultBackgroundColor = FC_RULE_NOTE_DEFAULT_BACKGROUND_COLOR;
readonly defaultApplyMarkdownStyle = FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE;
private static readonly HEADING_STYLE_OVERRIDE = '.tb-markdown-view h1 { font-size: 3rem; }';
private static readonly PADDING_STYLE_OVERRIDE = '.tb-markdown-view p, .tb-markdown-view h1, .tb-markdown-view h2, .tb-markdown-view h3, .tb-markdown-view h4, .tb-markdown-view h5, .tb-markdown-view h6 {padding-left: 0 !important;}';
applyDefault = FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE;
additionalStyles: string[]; additionalStyles: string[];
noteClass: string; noteClass: string;
note: FcRuleNote; note: FcRuleNote;
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.applyDefault = this.note?.applyDefaultMarkdownStyle ?? FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE;
this.processCss(); this.processCss();
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.note) { if (changes.note) {
this.applyDefault = this.note?.applyDefaultMarkdownStyle ?? FC_RULE_NOTE_DEFAULT_APPLY_MARKDOWN_STYLE;
this.processCss(); this.processCss();
} }
} }
private processCss(): void { private processCss(): void {
let cssString = this.note?.markdownCss; let cssString = this.note?.markdownCss;
const styles: string[] = [];
if (this.applyDefault) {
styles.push(RuleNoteComponent.HEADING_STYLE_OVERRIDE);
styles.push(RuleNoteComponent.PADDING_STYLE_OVERRIDE);
}
if (isNotEmptyStr(cssString)) { if (isNotEmptyStr(cssString)) {
const cssParser = new cssjs(); const cssParser = new cssjs();
this.noteClass = 'rule-note-' + hashCode(cssString); this.noteClass = 'rule-note-' + hashCode(cssString);
@ -59,18 +69,16 @@ export class RuleNoteComponent extends FcNoteComponent implements OnInit, OnChan
cssParser.testMode = false; cssParser.testMode = false;
const cssObjects = cssParser.applyNamespacing(cssString); const cssObjects = cssParser.applyNamespacing(cssString);
cssString = cssParser.getCSSForEditor(cssObjects); cssString = cssParser.getCSSForEditor(cssObjects);
this.additionalStyles = [cssString]; styles.push(cssString);
} else { } else {
this.noteClass = undefined; this.noteClass = undefined;
this.additionalStyles = undefined;
} }
this.additionalStyles = styles.length ? styles : undefined;
} }
noteEdit(event: MouseEvent): void { noteEdit(event: MouseEvent): void {
event.stopPropagation(); event.stopPropagation();
if (this.userNoteCallbacks?.noteEdit) { this.userNoteCallbacks?.noteEdit?.(event, this.note);
this.userNoteCallbacks.noteEdit(event, this.note);
}
} }
noteDelete(event: MouseEvent): void { noteDelete(event: MouseEvent): void {

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

@ -5172,7 +5172,6 @@
"edit-note": "Edit note", "edit-note": "Edit note",
"note-content": "Markdown/HTML content", "note-content": "Markdown/HTML content",
"note-details": "Note details", "note-details": "Note details",
"note-empty": "Empty note",
"note-background-color": "Background color", "note-background-color": "Background color",
"note-border": "Border", "note-border": "Border",
"note-apply-default-markdown-style": "Apply default markdown style", "note-apply-default-markdown-style": "Apply default markdown style",

5
ui-ngx/yarn.lock

@ -7989,8 +7989,9 @@ ngx-drag-drop@^20.0.1:
dependencies: dependencies:
tslib "^2.3.0" tslib "^2.3.0"
"ngx-flowchart@file:../../ngx-flowchart/dist/ngx-flowchart": "ngx-flowchart@https://github.com/thingsboard/ngx-flowchart.git#release/4.1.0":
version "4.0.0" version "4.1.0"
resolved "https://github.com/thingsboard/ngx-flowchart.git#a3dee1761ecaa96c7d0ca22b1c6538b9e27103af"
dependencies: dependencies:
tslib "^2.3.0" tslib "^2.3.0"

Loading…
Cancel
Save