Browse Source

UI: replace MainToolbar models with a HomeService and rewire dashboard/router-tabs/home-links to use it

pull/15888/head
Igor Kulikov 3 weeks ago
parent
commit
ff0e33c075
  1. 70
      ui-ngx/src/app/core/services/home.service.ts
  2. 5
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  3. 18
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  4. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.html
  5. 2
      ui-ngx/src/app/modules/home/components/dashboard-view/dashboard-view.component.html
  6. 25
      ui-ngx/src/app/modules/home/components/dashboard-view/dashboard-view.component.ts
  7. 2
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  8. 2
      ui-ngx/src/app/modules/home/components/router-tabs.component.html
  9. 6
      ui-ngx/src/app/modules/home/components/router-tabs.component.ts
  10. 2
      ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html
  11. 4
      ui-ngx/src/app/modules/home/home.component.html
  12. 47
      ui-ngx/src/app/modules/home/home.component.ts
  13. 28
      ui-ngx/src/app/modules/home/models/main-toolbar.models.ts
  14. 2
      ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html
  15. 32
      ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts
  16. 15
      ui-ngx/src/app/shared/components/toggle-header.component.scss

70
ui-ngx/src/app/core/services/home.service.ts

@ -0,0 +1,70 @@
///
/// Copyright © 2016-2026 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EventEmitter, Injectable } from '@angular/core';
import { ActiveComponentService } from '@core/services/active-component.service';
import { BehaviorSubject, Observable, shareReplay, Subject } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
import { RouterTabsComponent } from '@home/components/router-tabs.component';
import { isDefinedAndNotNull } from '@core/utils';
import { PageComponent } from '@shared/components/page.component';
@Injectable({
providedIn: 'root'
})
export class HomeService {
private hideMainToolbarSubject: Subject<boolean> = new BehaviorSubject<boolean>(false);
private hideLoadingBarSubject: Subject<boolean> = new BehaviorSubject<boolean>(false);
get hideMainToolbar$() {
return this.hideMainToolbarSubject.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
}
get hideLoadingBar$() {
return this.hideLoadingBarSubject.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
}
toggleSideBar = new EventEmitter();
constructor(private activeComponentService: ActiveComponentService) {
this.activeComponentService.onActiveComponentChanged().subscribe(activeComponent => {
Promise.resolve().then(() => {
this.activeComponentChanged(activeComponent);
});
});
}
public setHideMainToolbar(hide: boolean): void {
Promise.resolve().then(() => {
this.hideMainToolbarSubject.next(hide);
});
}
private activeComponentChanged(activeComponent: any) {
this.hideMainToolbarSubject.next(false);
let hideLoadingBar = false;
if (activeComponent && activeComponent instanceof RouterTabsComponent
&& isDefinedAndNotNull(activeComponent.activatedRoute?.snapshot?.data?.showMainLoadingBar)) {
hideLoadingBar = !activeComponent.activatedRoute.snapshot.data.showMainLoadingBar;
} else if (activeComponent && activeComponent instanceof PageComponent
&& isDefinedAndNotNull(activeComponent?.showMainLoadingBar)) {
hideLoadingBar = !activeComponent.showMainLoadingBar;
}
this.hideLoadingBarSubject.next(hideLoadingBar);
}
}

5
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -314,6 +314,11 @@
</div>
</div>
</tb-dashboard-toolbar>
@if (hideMainToolbar && toolbarOpened && (homeService.hideLoadingBar$ | async) === false && (isLoading$ | async)) {
<mat-progress-bar color="warn" style="z-index: 10; margin-bottom: -4px; width: 100%;" mode="indeterminate"
>
</mat-progress-bar>
}
</section>
<section class="tb-dashboard-container tb-absolute-fill"
tb-toast toastTarget="dashboardRoot"

18
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -149,7 +149,7 @@ import {
MoveWidgetsDialogResult
} from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
import { HttpStatusCode } from '@angular/common/http';
import { MainToolbarComponent } from '@home/models/main-toolbar.models';
import { HomeService } from '@core/services/home.service';
// @dynamic
@Component({
@ -160,7 +160,7 @@ import { MainToolbarComponent } from '@home/models/main-toolbar.models';
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class DashboardPageComponent extends PageComponent implements IDashboardController, HasDirtyFlag, MainToolbarComponent, OnInit, AfterViewInit, OnDestroy {
export class DashboardPageComponent extends PageComponent implements IDashboardController, HasDirtyFlag, OnInit, AfterViewInit, OnDestroy {
LayoutType = LayoutType;
@ -200,6 +200,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
return ((this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit) || (this.isEditingWidget || this.isAddingWidget);
}
@Input()
hideMainToolbar = true;
@Input()
syncStateWithQueryParam = true;
@ -322,9 +325,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
updateBreadcrumbs = new EventEmitter();
hideMainToolbar = true;
toggleSideBar = new EventEmitter();
private rxSubscriptions = new Array<Subscription>();
get toolbarOpened(): boolean {
@ -378,7 +378,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef,
public elRef: ElementRef,
private injector: Injector) {
private injector: Injector,
public homeService: HomeService) {
super(store);
if (isDefinedAndNotNull(this.embeddedValue)) {
this.embedded = this.embeddedValue;
@ -386,6 +387,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
ngOnInit() {
if (this.hideMainToolbar) {
this.homeService.setHideMainToolbar(true);
}
this.rxSubscriptions.push(this.route.data.subscribe(
(data) => {
let dashboardPageInitData: DashboardPageInitData;
@ -1754,7 +1758,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
toggleSidenav() {
this.toggleSideBar.emit();
this.homeService.toggleSideBar.emit();
}
get showMainLayoutFiller(): boolean {

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.html

@ -20,6 +20,7 @@
[embedded]="true"
[syncStateWithQueryParam]="false"
[hideToolbar]="true"
[hideMainToolbar]="false"
[currentState]="currentState"
[dashboard]="dashboard"
[parentDashboard]="parentDashboard"

2
ui-ngx/src/app/modules/home/components/dashboard-view/dashboard-view.component.html

@ -15,4 +15,4 @@
limitations under the License.
-->
<tb-dashboard-page #dashboardPage [embedded]="true" [dashboard]="dashboard"></tb-dashboard-page>
<tb-dashboard-page [embedded]="true" [dashboard]="dashboard"></tb-dashboard-page>

25
ui-ngx/src/app/modules/home/components/dashboard-view/dashboard-view.component.ts

@ -14,15 +14,12 @@
/// limitations under the License.
///
import { Component, DestroyRef, effect, EventEmitter, viewChild } from '@angular/core';
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { Dashboard } from '@shared/models/dashboard.models';
import { ActivatedRoute } from '@angular/router';
import { MainToolbarComponent } from '@home/models/main-toolbar.models';
import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-dashboard-view',
@ -30,29 +27,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
styleUrls: ['./dashboard-view.component.scss'],
standalone: false
})
export class DashboardViewComponent extends PageComponent implements MainToolbarComponent {
export class DashboardViewComponent extends PageComponent {
dashboard: Dashboard = this.route.snapshot.data.dashboard;
hideMainToolbar = true;
toggleSideBar = new EventEmitter<void>();
private dashboardPage = viewChild<DashboardPageComponent>('dashboardPage');
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
private destroyRef: DestroyRef) {
private route: ActivatedRoute) {
super(store);
effect(() => {
const dashboardPage = this.dashboardPage();
if (dashboardPage) {
dashboardPage.toggleSideBar.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.toggleSideBar.emit();
});
}
});
}
}

2
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html

@ -78,7 +78,7 @@
</mat-menu>
<div [class]="dashboardClass" id="gridster-background">
<gridster #gridster id="gridster-child" [options]="gridsterOpts">
@for (widget of dashboardWidgets; track widget) {
@for (widget of dashboardWidgets; track widget.widgetId) {
<gridster-item #gridsterItem [item]="widget" [class]="{'tb-noselect': isEdit}">
<tb-widget-container
[gridsterItem]="gridsterItem"

2
ui-ngx/src/app/modules/home/components/router-tabs.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="mt-2 flex flex-1 flex-col" style="width: 100%;">
<div class="flex flex-1 flex-col" [class.mt-2]="(homeService.hideMainToolbar$ | async) === false" style="width: 100%;">
<div class="flex flex-row" style="width: 100%;">
@if (!routerOutlet?.activatedRouteData?.hideTabs && !hideCurrentTabs && (tabs$ | async).length > 1) {
<nav mat-tab-nav-bar mat-stretch-tabs="false" class="tb-router-tabs flex-1" [tabPanel]="tabPanel">

6
ui-ngx/src/app/modules/home/components/router-tabs.component.ts

@ -26,6 +26,7 @@ import { MenuSection } from '@core/services/menu.models';
import { ActiveComponentService } from '@core/services/active-component.service';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { HomeService } from '@core/services/home.service';
@Component({
selector: 'tb-router-tabs',
@ -46,10 +47,11 @@ export class RouterTabsComponent extends PageComponent implements OnInit {
tabs$: Observable<Array<MenuSection>>;
constructor(protected store: Store<AppState>,
private activatedRoute: ActivatedRoute,
public activatedRoute: ActivatedRoute,
public router: Router,
private menuService: MenuService,
private activeComponentService: ActiveComponentService) {
private activeComponentService: ActiveComponentService,
public homeService: HomeService) {
super(store);
}

2
ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html

@ -30,7 +30,7 @@
<div style="height: 4px;"></div>
}
<div class="dashboard-state-dialog-content flex flex-1 flex-col" mat-dialog-content style="padding: 8px;">
<tb-dashboard-page [embedded]="true" [syncStateWithQueryParam]="false" [hideToolbar]="hideToolbar"
<tb-dashboard-page [embedded]="true" [syncStateWithQueryParam]="false" [hideToolbar]="hideToolbar" [hideMainToolbar]="false"
[currentState]="state" [dashboard]="dashboard" [parentDashboard]="parentDashboard" style="width: 100%; height: 100%;"></tb-dashboard-page>
</div>
<div mat-dialog-actions class="flex items-center justify-end">

4
ui-ngx/src/app/modules/home/home.component.html

@ -50,7 +50,7 @@
</mat-sidenav>
<mat-sidenav-content class="tb-site-content" [class.tb-force-fullscreen]="forceFullscreen">
<div class="flex flex-col" role="main" style="height: 100%;">
@if (!hideMainToolbar) {
@if ((homeService.hideMainToolbar$ | async) === false) {
<mat-toolbar class="tb-primary-toolbar flex flex-row">
<button mat-icon-button id="main"
[class.!hidden]="forceFullscreen || displaySearchMode()"
@ -96,7 +96,7 @@
<tb-user-menu></tb-user-menu>
}
</mat-toolbar>
@if (!hideLoadingBar && (isLoading$ | async)) {
@if ((homeService.hideLoadingBar$ | async) === false && (isLoading$ | async)) {
<mat-progress-bar color="warn" style="z-index: 10; margin-bottom: -4px; width: 100%;" mode="indeterminate"
>
</mat-progress-bar>

47
ui-ngx/src/app/modules/home/home.component.ts

@ -18,14 +18,14 @@ import {
AfterViewInit,
Component,
computed,
ElementRef, EventEmitter,
ElementRef,
Inject,
OnDestroy,
OnInit,
signal,
ViewChild
} from '@angular/core';
import { skip, startWith, Subject, Subscription } from 'rxjs';
import { skip, startWith, Subject } from 'rxjs';
import { select, Store } from '@ngrx/store';
import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators';
@ -44,7 +44,7 @@ import { RouterTabsComponent } from '@home/components/router-tabs.component';
import { FormBuilder } from '@angular/forms';
import { isDefinedAndNotNull } from '@core/utils';
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
import { isMainToolbarComponent } from '@home/models/main-toolbar.models';
import { HomeService } from '@core/services/home.service';
@Component({
selector: 'tb-home',
@ -84,19 +84,14 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
showSearch = false;
textSearch = this.fb.control('', {nonNullable: true});
hideLoadingBar = false;
hideMainToolbar = false;
private toggleSideBarSubscription: Subscription;
private destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
private activeComponentService: ActiveComponentService,
private fb: FormBuilder,
public breakpointObserver: BreakpointObserver) {
public breakpointObserver: BreakpointObserver,
public homeService: HomeService) {
super(store);
}
@ -127,12 +122,13 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
}
}
);
this.homeService.toggleSideBar.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.sidenav.toggle();
});
}
ngOnDestroy() {
if (this.toggleSideBarSubscription) {
this.toggleSideBarSubscription.unsubscribe();
}
this.destroy$.next();
this.destroy$.complete();
}
@ -186,18 +182,9 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
private updateActiveComponent(activeComponent: any) {
this.showSearch = false;
this.hideLoadingBar = false;
this.textSearch.reset('', {emitEvent: false});
this.activeComponent = activeComponent;
if (activeComponent && activeComponent instanceof RouterTabsComponent
&& isDefinedAndNotNull(this.activeComponent.activatedRoute?.snapshot?.data?.showMainLoadingBar)) {
this.hideLoadingBar = !this.activeComponent.activatedRoute.snapshot.data.showMainLoadingBar;
} else if (activeComponent && activeComponent instanceof PageComponent
&& isDefinedAndNotNull(this.activeComponent?.showMainLoadingBar)) {
this.hideLoadingBar = !this.activeComponent.showMainLoadingBar;
}
if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) {
this.searchEnabled = true;
this.searchableComponent = this.activeComponent;
@ -205,22 +192,6 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
this.searchEnabled = false;
this.searchableComponent = null;
}
if (this.toggleSideBarSubscription) {
this.toggleSideBarSubscription.unsubscribe();
this.toggleSideBarSubscription = null;
}
this.hideMainToolbar = false;
if (isMainToolbarComponent(this.activeComponent)) {
this.hideMainToolbar = this.activeComponent.hideMainToolbar;
this.toggleSideBarSubscription = this.activeComponent.toggleSideBar.subscribe(
() => {
this.sidenav.toggle();
}
);
}
}
displaySearchMode(): boolean {

28
ui-ngx/src/app/modules/home/models/main-toolbar.models.ts

@ -1,28 +0,0 @@
///
/// Copyright © 2016-2026 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EventEmitter } from '@angular/core';
import { isObservable } from 'rxjs';
export interface MainToolbarComponent {
hideMainToolbar: boolean;
toggleSideBar: EventEmitter<void>;
}
export const isMainToolbarComponent = (cmp: any): cmp is MainToolbarComponent => {
const mainToolbarCmp = (cmp as MainToolbarComponent);
return isObservable(mainToolbarCmp.toggleSideBar);
};

2
ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html

@ -16,7 +16,7 @@
-->
@if (homeDashboard) {
<tb-dashboard-page #dashboardPage [embedded]="true" [dashboard]="homeDashboard" [hideToolbar]="homeDashboard.hideDashboardToolbar"></tb-dashboard-page>
<tb-dashboard-page [embedded]="true" [dashboard]="homeDashboard" [hideToolbar]="homeDashboard.hideDashboardToolbar" [hideMainToolbar]="hideMainToolbar"></tb-dashboard-page>
} @else {
<mat-grid-list class="tb-home-links" [cols]="cols" rowHeight="280px">
@for (section of homeSections$| async; track section) {

32
ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts

@ -17,11 +17,8 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
effect,
EventEmitter,
OnInit,
viewChild
Component,
OnInit
} from '@angular/core';
import { MenuService } from '@core/services/menu.service';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
@ -29,9 +26,6 @@ import { MediaBreakpoints } from '@shared/models/constants';
import { HomeSection } from '@core/services/menu.models';
import { ActivatedRoute } from '@angular/router';
import { HomeDashboard } from '@shared/models/dashboard.models';
import { MainToolbarComponent } from '@home/models/main-toolbar.models';
import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-home-links',
@ -40,9 +34,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class HomeLinksComponent implements MainToolbarComponent, OnInit {
private dashboardPage = viewChild<DashboardPageComponent>('dashboardPage');
export class HomeLinksComponent implements OnInit {
homeSections$ = this.menuService.homeSections();
@ -50,28 +42,16 @@ export class HomeLinksComponent implements MainToolbarComponent, OnInit {
homeDashboard: HomeDashboard = this.route.snapshot.data.homeDashboard;
hideMainToolbar = false;
toggleSideBar = new EventEmitter<void>();
hideMainToolbar = true;
constructor(private menuService: MenuService,
public breakpointObserver: BreakpointObserver,
private cd: ChangeDetectorRef,
private route: ActivatedRoute,
private destroyRef: DestroyRef) {
effect(() => {
const dashboardPage = this.dashboardPage();
if (dashboardPage) {
dashboardPage.toggleSideBar.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.toggleSideBar.emit();
});
}
});
this.hideMainToolbar = (!!this.homeDashboard && !this.homeDashboard.isSystemDashboard);
private route: ActivatedRoute) {
}
ngOnInit() {
this.hideMainToolbar = (!!this.homeDashboard && !this.homeDashboard.isSystemDashboard);
if (!this.homeDashboard) {
this.updateColumnCount();
this.breakpointObserver

15
ui-ngx/src/app/shared/components/toggle-header.component.scss

@ -139,7 +139,6 @@
}
&.tb-invert {
.mat-button-toggle.mat-button-toggle-appearance-standard {
color: rgba(255, 255, 255, 0.8);
&.mat-button-toggle-checked {
.mat-button-toggle-button {
background: #FFFFFF;
@ -261,3 +260,17 @@
}
}
}
:host-context(.mat-primary) {
::ng-deep {
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header {
&.tb-fill {
&.tb-invert {
.mat-button-toggle.mat-button-toggle-appearance-standard {
color: rgba(255, 255, 255, 0.8);
}
}
}
}
}
}

Loading…
Cancel
Save