From 26f0028294521a3e2b7617d829a8509d9cb89b20 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 17 Oct 2024 16:26:29 +0300 Subject: [PATCH] Flex layout replacements. Switch to tailwind.css. --- ui-ngx/src/app/core/core.module.ts | 2 - .../dynamic-component-factory.service.ts | 9 +- .../app/core/services/resources.service.ts | 77 ++++++++++++++--- .../app/modules/common/modules-map.models.ts | 4 +- ui-ngx/src/app/modules/common/modules-map.ts | 68 ++++++++------- .../app/shared/layout/layout.directives.ts | 85 ------------------- .../app/shared/legacy/flex-layout.models.ts | 41 +++++++++ ui-ngx/src/app/shared/shared.module.ts | 31 ------- 8 files changed, 153 insertions(+), 164 deletions(-) delete mode 100644 ui-ngx/src/app/shared/layout/layout.directives.ts create mode 100644 ui-ngx/src/app/shared/legacy/flex-layout.models.ts diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index a8e2ccbe02..43f3f14df4 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -35,7 +35,6 @@ import { TbMissingTranslationHandler } from './translate/missing-translate-handl import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DEFAULT_OPTIONS, MatDialogConfig, MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; import { WINDOW_PROVIDERS } from '@core/services/window.service'; import { HotkeyModule } from 'angular2-hotkeys'; @@ -44,7 +43,6 @@ import { TranslateDefaultLoader } from '@core/translate/translate-default-loader import { EntityConflictInterceptor } from '@core/interceptors/entity-conflict.interceptor'; @NgModule({ exports: [], imports: [CommonModule, - FlexLayoutModule.withConfig({ addFlexToParent: false }), MatDialogModule, MatButtonModule, MatSnackBarModule, diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 40ac9c354b..cbf56dc44d 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -15,10 +15,11 @@ /// import { Component, Injectable, Type, ɵComponentDef, ɵNG_COMP_DEF } from '@angular/core'; -import { from, Observable, of } from 'rxjs'; +import { forkJoin, from, Observable, of } from 'rxjs'; import { CommonModule } from '@angular/common'; import { mergeMap } from 'rxjs/operators'; import { guid } from '@core/utils'; +import { getFlexLayoutModule } from '@shared/legacy/flex-layout.models'; @Injectable({ providedIn: 'root' @@ -34,9 +35,9 @@ export class DynamicComponentFactoryService { imports?: Type[], preserveWhitespaces?: boolean, styles?: string[]): Observable> { - return from(import('@angular/compiler')).pipe( - mergeMap(() => { - let componentImports: Type[] = [CommonModule]; + return forkJoin({flexLayoutModule: getFlexLayoutModule(), compiler: from(import('@angular/compiler'))}).pipe( + mergeMap((data) => { + let componentImports: Type[] = [CommonModule, data.flexLayoutModule]; if (imports) { componentImports = [...componentImports, ...imports]; } diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 8088cf0736..21bd11293e 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -27,7 +27,7 @@ import { ɵNgModuleDef } from '@angular/core'; import { DOCUMENT } from '@angular/common'; -import { Observable, ReplaySubject, throwError } from 'rxjs'; +import { forkJoin, from, Observable, ReplaySubject, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { IModulesMap } from '@modules/common/modules-map.models'; import { TbResourceId } from '@shared/models/id/tb-resource-id'; @@ -38,6 +38,7 @@ import { selectIsAuthenticated } from '@core/auth/auth.selectors'; import { AppState } from '@core/core.state'; import { map, tap } from 'rxjs/operators'; import { RequestConfig } from '@core/http/http-utils'; +import { getFlexLayoutModule } from '@app/shared/legacy/flex-layout.models'; export interface ModuleInfo { module: ɵNgModuleDef; @@ -187,26 +188,46 @@ export class ResourcesService { if (this.loadedModulesWithComponents[url]) { return this.loadedModulesWithComponents[url].asObservable(); } - modulesMap.init(); const meta = this.getMetaInfo(resourceId); const subject = new ReplaySubject(); this.loadedModulesWithComponents[url] = subject; - import('@angular/compiler').then( + + forkJoin( + [ + modulesMap.init(), + from(import('@angular/compiler')) + ] + ).subscribe( () => { // @ts-ignore System.import(url, undefined, meta).then( (module: any) => { try { const modulesWithComponents = this.extractModulesWithComponents(module); - if (modulesWithComponents.modules.length || modulesWithComponents.standaloneComponents.length) { - for (const module of modulesWithComponents.modules) { - createNgModule(module.module.type, this.injector); + this.patchModulesWithFlexLayout(modulesWithComponents).subscribe( + { + next: modules => { + if (modules.modules.length || modules.standaloneComponents.length) { + try { + for (const module of modules.modules) { + createNgModule(module.module.type, this.injector); + } + this.loadedModulesWithComponents[url].next(modulesWithComponents); + this.loadedModulesWithComponents[url].complete(); + } catch (e) { + console.log(`Unable to parse module from url: ${url}`, e); + this.loadedModulesWithComponents[url].error(new Error(`Unable to parse module from url: ${url}`)); + } + } else { + this.loadedModulesWithComponents[url].error(new Error(`Module '${url}' doesn't have exported modules or components!`)); + } + }, + error: err => { + console.log(`Unable to patch module with flexLayout, module url: ${url}`, err); + this.loadedModulesWithComponents[url].error(new Error(`Unable to patch module with flexLayout, module url: ${url}`)); + } } - this.loadedModulesWithComponents[url].next(modulesWithComponents); - this.loadedModulesWithComponents[url].complete(); - } else { - this.loadedModulesWithComponents[url].error(new Error(`Module '${url}' doesn't have exported modules or components!`)); - } + ); } catch (e) { console.log(`Unable to parse module from url: ${url}`, e); this.loadedModulesWithComponents[url].error(new Error(`Unable to parse module from url: ${url}`)); @@ -284,6 +305,40 @@ export class ResourcesService { return modulesWithComponents; } + private patchModulesWithFlexLayout(modulesWithComponents: ModulesWithComponents): Observable { + return getFlexLayoutModule().pipe( + map((flexLayoutModule) => { + modulesWithComponents.modules.forEach(m => { + if (Array.isArray(m.module.imports)) { + if (!m.module.imports.includes(flexLayoutModule)) { + m.module.imports.push(flexLayoutModule); + } + } else { + const imports = m.module.imports(); + if (!imports.includes(flexLayoutModule)) { + imports.push(flexLayoutModule); + m.module.imports = imports; + } + } + }); + modulesWithComponents.standaloneComponents.forEach(c => { + if (Array.isArray(c.dependencies)) { + if (!c.dependencies.includes(flexLayoutModule)) { + c.dependencies.push(flexLayoutModule); + } + } else { + const dependencies = c.dependencies(); + if (!dependencies.includes(flexLayoutModule)) { + dependencies.push(flexLayoutModule); + c.dependencies = dependencies; + } + } + }); + return modulesWithComponents; + }) + ); + } + private loadResourceByType(type: 'css' | 'js', url: string): Observable { const subject = new ReplaySubject(); this.loadedResources[url] = subject; diff --git a/ui-ngx/src/app/modules/common/modules-map.models.ts b/ui-ngx/src/app/modules/common/modules-map.models.ts index e1c88e557a..8913996851 100644 --- a/ui-ngx/src/app/modules/common/modules-map.models.ts +++ b/ui-ngx/src/app/modules/common/modules-map.models.ts @@ -14,6 +14,8 @@ /// limitations under the License. /// +import { Observable } from 'rxjs'; + export interface IModulesMap { - init(): void; + init(): Observable; } diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 76bc84bbec..4ca23753c6 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -20,10 +20,6 @@ import * as AngularAnimations from '@angular/animations'; import * as AngularCore from '@angular/core'; import * as AngularCommon from '@angular/common'; import * as AngularForms from '@angular/forms'; -import * as AngularFlexLayout from '@angular/flex-layout'; -import * as AngularFlexLayoutFlex from '@angular/flex-layout/flex'; -import * as AngularFlexLayoutGrid from '@angular/flex-layout/grid'; -import * as AngularFlexLayoutExtended from '@angular/flex-layout/extended'; import * as AngularPlatformBrowser from '@angular/platform-browser'; import * as AngularPlatformBrowserAnimations from '@angular/platform-browser/animations'; import * as AngularRouter from '@angular/router'; @@ -338,6 +334,8 @@ import { IModulesMap } from '@modules/common/modules-map.models'; import { TimezoneComponent } from '@shared/components/time/timezone.component'; import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; +import { Observable, map, of } from 'rxjs'; +import { getFlexLayout } from '@shared/legacy/flex-layout.models'; class ModulesMap implements IModulesMap { @@ -349,10 +347,10 @@ class ModulesMap implements IModulesMap { '@angular/common': AngularCommon, '@angular/common/http': HttpClientModule, '@angular/forms': AngularForms, - '@angular/flex-layout': AngularFlexLayout, - '@angular/flex-layout/flex': AngularFlexLayoutFlex, - '@angular/flex-layout/grid': AngularFlexLayoutGrid, - '@angular/flex-layout/extended': AngularFlexLayoutExtended, + '@angular/flex-layout': {}, + '@angular/flex-layout/flex': {}, + '@angular/flex-layout/grid': {}, + '@angular/flex-layout/extended': {}, '@angular/platform-browser': AngularPlatformBrowser, '@angular/platform-browser/animations': AngularPlatformBrowserAnimations, '@angular/router': AngularRouter, @@ -669,30 +667,40 @@ class ModulesMap implements IModulesMap { '@home/components/queue/queue-form.component': QueueFormComponent }; - init() { + init(): Observable { if (!this.initialized) { - System.constructor.prototype.resolve = (id: string) => { - try { - if (this.modulesMap[id]) { - return 'app:' + id; - } else { - return id; + return getFlexLayout().pipe( + map((flexLayout) => { + this.modulesMap['@angular/flex-layout'] = flexLayout; + this.modulesMap['@angular/flex-layout/flex'] = flexLayout; + this.modulesMap['@angular/flex-layout/grid'] = flexLayout; + this.modulesMap['@angular/flex-layout/extended'] = flexLayout; + System.constructor.prototype.resolve = (id: string) => { + try { + if (this.modulesMap[id]) { + return 'app:' + id; + } else { + return id; + } + } catch (err) { + return id; + } + }; + for (const moduleId of Object.keys(this.modulesMap)) { + System.set('app:' + moduleId, this.modulesMap[moduleId]); } - } catch (err) { - return id; - } - }; - for (const moduleId of Object.keys(this.modulesMap)) { - System.set('app:' + moduleId, this.modulesMap[moduleId]); - } - System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); - System.constructor.prototype.fetch = (url: string, options: RequestInit & {meta?: any}) => { - if (options?.meta?.additionalHeaders) { - options.headers = { ...options.headers, ...options.meta.additionalHeaders }; - } - return fetch(url, options); - }; - this.initialized = true; + System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); + System.constructor.prototype.fetch = (url: string, options: RequestInit & {meta?: any}) => { + if (options?.meta?.additionalHeaders) { + options.headers = { ...options.headers, ...options.meta.additionalHeaders }; + } + return fetch(url, options); + }; + this.initialized = true; + }) + ); + } else { + return of(null); } } } diff --git a/ui-ngx/src/app/shared/layout/layout.directives.ts b/ui-ngx/src/app/shared/layout/layout.directives.ts deleted file mode 100644 index 22b8430662..0000000000 --- a/ui-ngx/src/app/shared/layout/layout.directives.ts +++ /dev/null @@ -1,85 +0,0 @@ -/// -/// Copyright © 2016-2024 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 { Directive } from '@angular/core'; -import { BREAKPOINT, LayoutAlignDirective, LayoutDirective, LayoutGapDirective, ShowHideDirective } from '@angular/flex-layout'; - -const TB_BREAKPOINTS = [ - { - alias: 'md-lg', - mediaQuery: 'screen and (min-width: 960px) and (max-width: 1819px)', - priority: 750 - }, - { - alias: 'gt-md-lg', - mediaQuery: 'screen and (min-width: 1820px)', - priority: -600 - } -]; - -export const TbBreakPointsProvider = { - provide: BREAKPOINT, - useValue: TB_BREAKPOINTS, - multi: true -}; - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayout.md-lg]', inputs: ['fxLayout.md-lg']}) -export class MdLgLayoutDirective extends LayoutDirective { - protected inputs = ['fxLayout.md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayoutAlign.md-lg]', inputs: ['fxLayoutAlign.md-lg']}) -export class MdLgLayoutAlignDirective extends LayoutAlignDirective { - protected inputs = ['fxLayoutAlign.md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayoutGap.md-lg]', inputs: ['fxLayoutGap.md-lg']}) -export class MdLgLayoutGapDirective extends LayoutGapDirective { - protected inputs = ['fxLayoutGap.md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxHide.md-lg]', inputs: ['fxHide.md-lg']}) -export class MdLgShowHideDirective extends ShowHideDirective { - protected inputs = ['fxHide.md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayout.gt-md-lg]', inputs: ['fxLayout.gt-md-lg']}) -export class GtMdLgLayoutDirective extends LayoutDirective { - protected inputs = ['fxLayout.gt-md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayoutAlign.gt-md-lg]', inputs: ['fxLayoutAlign.gt-md-lg']}) -export class GtMdLgLayoutAlignDirective extends LayoutAlignDirective { - protected inputs = ['fxLayoutAlign.gt-md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxLayoutGap.gt-md-lg]', inputs: ['fxLayoutGap.gt-md-lg']}) -export class GtMdLgLayoutGapDirective extends LayoutGapDirective { - protected inputs = ['fxLayoutGap.gt-md-lg']; -} - -// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property,@angular-eslint/directive-selector -@Directive({selector: '[fxHide.gt-md-lg]', inputs: ['fxHide.gt-md-lg']}) -export class GtMdLgShowHideDirective extends ShowHideDirective { - protected inputs = ['fxHide.gt-md-lg']; -} diff --git a/ui-ngx/src/app/shared/legacy/flex-layout.models.ts b/ui-ngx/src/app/shared/legacy/flex-layout.models.ts new file mode 100644 index 0000000000..5e00ae1f06 --- /dev/null +++ b/ui-ngx/src/app/shared/legacy/flex-layout.models.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2024 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 { Observable } from 'rxjs/internal/Observable'; +import { from, of } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { Type } from '@angular/core'; + +let flexLayoutModule: any; + +export function getFlexLayout(): Observable { + if (flexLayoutModule) { + return of(flexLayoutModule); + } else { + return from(import('@angular/flex-layout')).pipe( + tap((module) => { + module.DEFAULT_CONFIG.addFlexToParent = false; + flexLayoutModule = module; + }) + ); + } +} + +export function getFlexLayoutModule(): Observable> { + return getFlexLayout().pipe( + map(module => module.FlexLayoutModule) + ); +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index c715377e92..8bb22b83df 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -58,7 +58,6 @@ import { MatListModule } from '@angular/material/list'; import { DatetimeAdapter, MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { NgxDaterangepickerMd } from 'ngx-daterangepicker-material'; import { GridsterModule } from 'angular-gridster2'; -import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { ShareButtonDirective } from 'ngx-sharebuttons'; @@ -179,17 +178,6 @@ import { NotificationComponent } from '@shared/components/notification/notificat import { TemplateAutocompleteComponent } from '@shared/components/notification/template-autocomplete.component'; import { SlackConversationAutocompleteComponent } from '@shared/components/slack-conversation-autocomplete.component'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; -import { - GtMdLgLayoutAlignDirective, - GtMdLgLayoutDirective, - GtMdLgLayoutGapDirective, - GtMdLgShowHideDirective, - MdLgLayoutAlignDirective, - MdLgLayoutDirective, - MdLgLayoutGapDirective, - MdLgShowHideDirective, - TbBreakPointsProvider -} from '@shared/layout/layout.directives'; import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; import { ResourceAutocompleteComponent } from '@shared/components/resource/resource-autocomplete.component'; import { ShortNumberPipe } from '@shared/pipe/short-number.pipe'; @@ -286,7 +274,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) disableTooltipInteractivity: true } }, - TbBreakPointsProvider, CountryData ], declarations: [ @@ -404,14 +391,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) NotificationComponent, TemplateAutocompleteComponent, SlackConversationAutocompleteComponent, - MdLgLayoutDirective, - MdLgLayoutAlignDirective, - MdLgLayoutGapDirective, - MdLgShowHideDirective, - GtMdLgLayoutDirective, - GtMdLgLayoutAlignDirective, - GtMdLgLayoutGapDirective, - GtMdLgShowHideDirective, ColorPickerComponent, ColorPickerPanelComponent, ResourceAutocompleteComponent, @@ -484,7 +463,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) DragDropModule, GridsterModule, ClipboardModule, - FlexLayoutModule.withConfig({addFlexToParent: false}), FormsModule, ReactiveFormsModule, OverlayModule, @@ -606,7 +584,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) DragDropModule, GridsterModule, ClipboardModule, - FlexLayoutModule, FormsModule, ReactiveFormsModule, OverlayModule, @@ -671,14 +648,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) NotificationComponent, TemplateAutocompleteComponent, SlackConversationAutocompleteComponent, - MdLgLayoutDirective, - MdLgLayoutAlignDirective, - MdLgLayoutGapDirective, - MdLgShowHideDirective, - GtMdLgLayoutDirective, - GtMdLgLayoutAlignDirective, - GtMdLgLayoutGapDirective, - GtMdLgShowHideDirective, ColorPickerComponent, ColorPickerPanelComponent, ResourceAutocompleteComponent,