Flex layout replacements. Switch to tailwind.css.

This commit is contained in:
Igor Kulikov 2024-10-17 16:26:29 +03:00
parent 3a5ab479f0
commit 26f0028294
8 changed files with 153 additions and 164 deletions

View File

@ -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,

View File

@ -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<any>[],
preserveWhitespaces?: boolean,
styles?: string[]): Observable<Type<T>> {
return from(import('@angular/compiler')).pipe(
mergeMap(() => {
let componentImports: Type<any>[] = [CommonModule];
return forkJoin({flexLayoutModule: getFlexLayoutModule(), compiler: from(import('@angular/compiler'))}).pipe(
mergeMap((data) => {
let componentImports: Type<any>[] = [CommonModule, data.flexLayoutModule];
if (imports) {
componentImports = [...componentImports, ...imports];
}

View File

@ -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<any>;
@ -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<ModulesWithComponents>();
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<ModulesWithComponents> {
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<any> {
const subject = new ReplaySubject<void>();
this.loadedResources[url] = subject;

View File

@ -14,6 +14,8 @@
/// limitations under the License.
///
import { Observable } from 'rxjs';
export interface IModulesMap {
init(): void;
init(): Observable<any>;
}

View File

@ -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<any> {
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);
}
}
}

View File

@ -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'];
}

View File

@ -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<any> {
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<Type<any>> {
return getFlexLayout().pipe(
map(module => module.FlexLayoutModule)
);
}

View File

@ -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,