diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 60d05d4c86..a839e98c13 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -58,7 +58,7 @@ import { } from '@app/shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; import { WindowMessage } from '@shared/models/window-message.model'; -import { deepClone, guid, isDefined, isDefinedAndNotNull, isEqual, isNotEmptyStr } from '@app/core/utils'; +import { deepClone, guid, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@app/core/utils'; import { DashboardContext, DashboardPageInitData, @@ -119,7 +119,8 @@ import { } from '@home/components/dashboard-page/dashboard-settings-dialog.component'; import { ManageDashboardStatesDialogComponent, - ManageDashboardStatesDialogData + ManageDashboardStatesDialogData, + ManageDashboardStatesDialogResult } from '@home/components/dashboard-page/states/manage-dashboard-states-dialog.component'; import { ImportExportService } from '@shared/import-export/import-export.service'; import { AuthState } from '@app/core/auth/auth.models'; @@ -964,17 +965,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC $event.stopPropagation(); } this.dialog.open(ManageDashboardStatesDialogComponent, { + ManageDashboardStatesDialogResult>(ManageDashboardStatesDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { states: deepClone(this.dashboard.configuration.states), - widgets: deepClone(this.dashboard.configuration.widgets) as {[id: string]: Widget} + widgets: this.dashboard.configuration.widgets as {[id: string]: Widget} } }).afterClosed().subscribe((result) => { if (result) { - if (!isEqual(result.widgets, this.dashboard.configuration.widgets)) { - this.dashboard.configuration.widgets = result.widgets; + if (result.addWidgets) { + Object.assign(this.dashboard.configuration.widgets, result.addWidgets); } if (result.states) { this.updateStates(result.states); diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.html index a2b0a4c0f3..b91a62c8e7 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.html @@ -15,143 +15,134 @@ limitations under the License. --> -
- -

dashboard.manage-states

- - -
- - -
-
-
-
-
- -
- dashboard.states - - - -
-
- -
- - -   - - - -
-
-
- - - {{ 'dashboard.state-name' | translate }} - - {{ state.name }} - - - - {{ 'dashboard.state-id' | translate }} - - {{ state.id }} - - - - {{ 'dashboard.is-root-state' | translate }} - - {{state.root ? 'check_box' : 'check_box_outline_blank'}} - - - - - - -
- - - -
-
-
- - -
- {{ 'dashboard.no-states-text' }} -
- - -
-
+ +

dashboard.manage-states

+ + +
+
+
+ +
+ dashboard.states + + +
-
+ + +
+ + +   + + + +
+
+
+ + + {{ 'dashboard.state-name' | translate }} + + {{ state.name }} + + + + {{ 'dashboard.state-id' | translate }} + + {{ state.id }} + + + + {{ 'dashboard.is-root-state' | translate }} + + {{state.root ? 'check_box' : 'check_box_outline_blank'}} + + + + + + +
+ + + +
+
+
+ + +
+ {{ 'dashboard.no-states-text' }} +
+ +
-
- - -
-
+ +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.scss index 862737c22b..98d48861bd 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.scss @@ -13,27 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import "../scss/constants"; + :host { + height: 100%; + display: grid; + grid-template-rows: min-content auto min-content; + .manage-dashboard-states { - .tb-entity-table { - .tb-entity-table-content { - width: 100%; - height: 100%; - background: #fff; + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; - .tb-entity-table-title { - padding-right: 20px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } - .table-container { - overflow: auto; - } + .table-container { + overflow: auto; } } } + + @media #{$mat-sm} { + min-width: 470px; + } + + @media #{$mat-gt-sm} { + min-width: 750px; + } } :host ::ng-deep { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.ts index 7dddc18dbd..6e74ee9b14 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/manage-dashboard-states-dialog.component.ts @@ -14,21 +14,10 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ElementRef, - Inject, - OnInit, - SecurityContext, - SkipSelf, - ViewChild -} from '@angular/core'; -import { ErrorStateMatcher } from '@angular/material/core'; +import { AfterViewInit, Component, ElementRef, Inject, OnInit, SecurityContext, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormGroupDirective, NgForm, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { DashboardState } from '@app/shared/models/dashboard.models'; @@ -44,7 +33,7 @@ import { fromEvent, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { DialogService } from '@core/services/dialog.service'; -import { deepClone, isDefined } from '@core/utils'; +import { deepClone, isDefined, isEqual } from '@core/utils'; import { DashboardStateDialogComponent, DashboardStateDialogData @@ -58,42 +47,42 @@ export interface ManageDashboardStatesDialogData { widgets: {[id: string]: Widget }; } +export interface ManageDashboardStatesDialogResult { + states: {[id: string]: DashboardState }; + addWidgets?: {[id: string]: Widget }; +} + @Component({ selector: 'tb-manage-dashboard-states-dialog', templateUrl: './manage-dashboard-states-dialog.component.html', - providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardStatesDialogComponent}], styleUrls: ['./manage-dashboard-states-dialog.component.scss'] }) export class ManageDashboardStatesDialogComponent - extends DialogComponent - implements OnInit, ErrorStateMatcher, AfterViewInit { + extends DialogComponent + implements OnInit, AfterViewInit { - statesFormGroup: UntypedFormGroup; + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; - states: {[id: string]: DashboardState }; - widgets: {[id: string]: Widget}; + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + isDirty = false; displayedColumns: string[]; pageLink: PageLink; textSearchMode = false; dataSource: DashboardStatesDatasource; - submitted = false; + private states: {[id: string]: DashboardState }; + private widgets: {[id: string]: Widget}; - stateNames: Set = new Set(); - - @ViewChild('searchInput') searchInputField: ElementRef; - - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; + private stateNames: Set = new Set(); + private addWidgets: {[id: string]: Widget} = {}; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ManageDashboardStatesDialogData, - @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public dialogRef: MatDialogRef, - private fb: UntypedFormBuilder, + public dialogRef: MatDialogRef, private translate: TranslateService, private dialogs: DialogService, private utils: UtilsService, @@ -103,7 +92,6 @@ export class ManageDashboardStatesDialogComponent this.states = this.data.states; this.widgets = this.data.widgets; - this.statesFormGroup = this.fb.group({}); Object.values(this.states).forEach(value => this.stateNames.add(value.name)); const sortOrder: SortOrder = { property: 'name', direction: Direction.ASC }; @@ -258,77 +246,65 @@ export class ManageDashboardStatesDialogComponent } duplicateState($event: Event, state: DashboardStateInfo) { - const originalState = state; - const newStateName = this.getNextDuplicatedName(state.name); - if (newStateName) { - const newStateId = newStateName.toLowerCase().replace(/\W/g, '_'); - if (this.states[newStateId]) { - this.stateNames.add(newStateName); - this.duplicateState(null, state); - } - const duplicatedStates = deepClone(originalState); - const duplicatedWidgets = deepClone(this.widgets); - const mainWidgets = {}; - const rightWidgets = {}; - duplicatedStates.id = newStateId; - duplicatedStates.name = newStateName; - duplicatedStates.root = false; - this.stateNames.add(duplicatedStates.name); - - for (const [key, value] of Object.entries(duplicatedStates.layouts.main.widgets)) { - const guid = this.utils.guid(); - mainWidgets[guid] = value; - duplicatedWidgets[guid] = this.widgets[key]; - duplicatedWidgets[guid].id = guid; - } - duplicatedStates.layouts.main.widgets = mainWidgets; - - if (isDefined(duplicatedStates.layouts?.right)) { - for (const [key, value] of Object.entries(duplicatedStates.layouts.right.widgets)) { - const guid = this.utils.guid(); - rightWidgets[guid] = value; - duplicatedWidgets[guid] = this.widgets[key]; - duplicatedWidgets[guid].id = guid; - } - duplicatedStates.layouts.right.widgets = rightWidgets; - } - - this.states[duplicatedStates.id] = duplicatedStates; - this.widgets = duplicatedWidgets; - this.onStatesUpdated(); - } - } - - private getNextDuplicatedName(stateName: string): string { const suffix = ` - ${this.translate.instant('action.copy')} `; let counter = 0; - while (++counter < Number.MAX_SAFE_INTEGER) { - const newName = `${stateName}${suffix}${counter}`; - if (!this.stateNames.has(newName)) { - return newName; - } - } + const maxAttempts = 1000; - return null; + while (counter++ < maxAttempts) { + const candidateName = `${state.name}${suffix}${counter}`; + if (this.stateNames.has(candidateName)) continue; + + const candidateId = candidateName.toLowerCase().replace(/\W/g, '_'); + if (this.states[candidateId]) { + continue; + } + + const duplicatedState = deepClone(state); + const mainWidgets = {}; + const rightWidgets = {}; + duplicatedState.id = candidateId; + duplicatedState.name = candidateName; + duplicatedState.root = false; + this.stateNames.add(duplicatedState.name); + + for (const [key, value] of Object.entries(duplicatedState.layouts.main.widgets)) { + const guid = this.utils.guid(); + mainWidgets[guid] = value; + this.addWidgets[guid] = deepClone(this.widgets[key] ?? this.addWidgets[key]); + this.addWidgets[guid].id = guid; + } + duplicatedState.layouts.main.widgets = mainWidgets; + + if (isDefined(duplicatedState.layouts?.right)) { + for (const [key, value] of Object.entries(duplicatedState.layouts.right.widgets)) { + const guid = this.utils.guid(); + rightWidgets[guid] = value; + this.addWidgets[guid] = deepClone(this.widgets[key] ?? this.addWidgets[key]); + this.addWidgets[guid].id = guid; + } + duplicatedState.layouts.right.widgets = rightWidgets; + } + + this.states[duplicatedState.id] = duplicatedState; + this.onStatesUpdated(); + return; + } } private onStatesUpdated() { - this.statesFormGroup.markAsDirty(); + this.isDirty = true; this.updateData(true); } - isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const originalErrorState = this.errorStateMatcher.isErrorState(control, form); - const customErrorState = !!(control && control.invalid && this.submitted); - return originalErrorState || customErrorState; - } - cancel(): void { this.dialogRef.close(null); } save(): void { - this.submitted = true; - this.dialogRef.close({ states: this.states, widgets: this.widgets }); + const result: ManageDashboardStatesDialogResult = {states: this.states}; + if (!isEqual(this.addWidgets, {})) { + result.addWidgets = this.addWidgets; + } + this.dialogRef.close(result); } }