@lukfel/ng-scaffold 20.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +37 -0
- package/README.md +382 -0
- package/ng-package.json +8 -0
- package/package.json +28 -0
- package/src/lib/components/bottom-bar/bottom-bar.component.html +29 -0
- package/src/lib/components/bottom-bar/bottom-bar.component.scss +33 -0
- package/src/lib/components/bottom-bar/bottom-bar.component.spec.ts +24 -0
- package/src/lib/components/bottom-bar/bottom-bar.component.ts +31 -0
- package/src/lib/components/content-title-card/content-title-card.component.html +25 -0
- package/src/lib/components/content-title-card/content-title-card.component.scss +17 -0
- package/src/lib/components/content-title-card/content-title-card.component.spec.ts +108 -0
- package/src/lib/components/content-title-card/content-title-card.component.ts +24 -0
- package/src/lib/components/drawer/drawer.component.html +33 -0
- package/src/lib/components/drawer/drawer.component.scss +10 -0
- package/src/lib/components/drawer/drawer.component.spec.ts +52 -0
- package/src/lib/components/drawer/drawer.component.ts +30 -0
- package/src/lib/components/floating-button/floating-button.component.html +32 -0
- package/src/lib/components/floating-button/floating-button.component.scss +20 -0
- package/src/lib/components/floating-button/floating-button.component.spec.ts +84 -0
- package/src/lib/components/floating-button/floating-button.component.ts +57 -0
- package/src/lib/components/footer/footer.component.html +38 -0
- package/src/lib/components/footer/footer.component.scss +39 -0
- package/src/lib/components/footer/footer.component.spec.ts +118 -0
- package/src/lib/components/footer/footer.component.ts +14 -0
- package/src/lib/components/header/header.component.html +170 -0
- package/src/lib/components/header/header.component.scss +102 -0
- package/src/lib/components/header/header.component.spec.ts +134 -0
- package/src/lib/components/header/header.component.ts +53 -0
- package/src/lib/components/loading-overlay/loading-overlay.component.html +3 -0
- package/src/lib/components/loading-overlay/loading-overlay.component.scss +16 -0
- package/src/lib/components/loading-overlay/loading-overlay.component.spec.ts +24 -0
- package/src/lib/components/loading-overlay/loading-overlay.component.ts +10 -0
- package/src/lib/components/navbar/navbar.component.html +43 -0
- package/src/lib/components/navbar/navbar.component.scss +71 -0
- package/src/lib/components/navbar/navbar.component.spec.ts +43 -0
- package/src/lib/components/navbar/navbar.component.ts +35 -0
- package/src/lib/components/scaffold/scaffold.component.html +74 -0
- package/src/lib/components/scaffold/scaffold.component.scss +48 -0
- package/src/lib/components/scaffold/scaffold.component.spec.ts +119 -0
- package/src/lib/components/scaffold/scaffold.component.ts +191 -0
- package/src/lib/interceptors/loading.interceptor.ts +51 -0
- package/src/lib/models/bottom-bar-config.model.ts +8 -0
- package/src/lib/models/confirm-dialog-config.model.ts +6 -0
- package/src/lib/models/content-title-card-config.model.ts +6 -0
- package/src/lib/models/drawer-config.model.ts +6 -0
- package/src/lib/models/floating-button-config.model.ts +13 -0
- package/src/lib/models/footer-config.model.ts +10 -0
- package/src/lib/models/header-config.model.ts +26 -0
- package/src/lib/models/index.ts +15 -0
- package/src/lib/models/library-config.model.ts +4 -0
- package/src/lib/models/menu-button.model.ts +10 -0
- package/src/lib/models/navbar-config.model.ts +8 -0
- package/src/lib/models/navigation-link.model.ts +6 -0
- package/src/lib/models/placeholder-config.model.ts +7 -0
- package/src/lib/models/scaffold-config.model.ts +21 -0
- package/src/lib/models/seo-config.model.ts +6 -0
- package/src/lib/scaffold.module.ts +54 -0
- package/src/lib/services/breakpoint.service.spec.ts +15 -0
- package/src/lib/services/breakpoint.service.ts +16 -0
- package/src/lib/services/dialog.service.spec.ts +18 -0
- package/src/lib/services/dialog.service.ts +58 -0
- package/src/lib/services/index.ts +9 -0
- package/src/lib/services/local-storage.service.spec.ts +15 -0
- package/src/lib/services/local-storage.service.ts +125 -0
- package/src/lib/services/logger.service.spec.ts +15 -0
- package/src/lib/services/logger.service.ts +46 -0
- package/src/lib/services/router.service.spec.ts +15 -0
- package/src/lib/services/router.service.ts +91 -0
- package/src/lib/services/scaffold.service.spec.ts +15 -0
- package/src/lib/services/scaffold.service.ts +77 -0
- package/src/lib/services/seo.service.spec.ts +15 -0
- package/src/lib/services/seo.service.ts +75 -0
- package/src/lib/services/snackbar.service.spec.ts +18 -0
- package/src/lib/services/snackbar.service.ts +38 -0
- package/src/lib/services/theme.service.spec.ts +20 -0
- package/src/lib/services/theme.service.ts +71 -0
- package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.html +24 -0
- package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.scss +0 -0
- package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.spec.ts +85 -0
- package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.ts +14 -0
- package/src/lib/shared/components/file-upload/file-upload.component.html +21 -0
- package/src/lib/shared/components/file-upload/file-upload.component.scss +5 -0
- package/src/lib/shared/components/file-upload/file-upload.component.spec.ts +25 -0
- package/src/lib/shared/components/file-upload/file-upload.component.ts +43 -0
- package/src/lib/shared/components/icon/icon.component.html +17 -0
- package/src/lib/shared/components/icon/icon.component.scss +9 -0
- package/src/lib/shared/components/icon/icon.component.spec.ts +22 -0
- package/src/lib/shared/components/icon/icon.component.ts +17 -0
- package/src/lib/shared/components/input/input.component.html +38 -0
- package/src/lib/shared/components/input/input.component.scss +31 -0
- package/src/lib/shared/components/input/input.component.spec.ts +62 -0
- package/src/lib/shared/components/input/input.component.ts +72 -0
- package/src/lib/shared/components/placeholder/placeholder.component.html +21 -0
- package/src/lib/shared/components/placeholder/placeholder.component.scss +30 -0
- package/src/lib/shared/components/placeholder/placeholder.component.spec.ts +24 -0
- package/src/lib/shared/components/placeholder/placeholder.component.ts +16 -0
- package/src/lib/shared/modules/material.module.ts +77 -0
- package/src/lib/shared/shared.module.ts +18 -0
- package/src/public-api.ts +16 -0
- package/styles/_classes.scss +34 -0
- package/styles/_theme.scss +97 -0
- package/styles/_variables.scss +50 -0
- package/styles/fonts/icons/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 +0 -0
- package/styles/fonts/icons/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2 +0 -0
- package/styles/fonts/material-icons.scss +48 -0
- package/styles/fonts/roboto/roboto-v30-latin-100.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-100.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-100italic.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-100italic.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-300.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-300.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-300italic.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-300italic.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-500.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-500.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-500italic.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-500italic.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-700.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-700.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-700italic.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-700italic.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-italic.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-italic.woff2 +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-regular.woff +0 -0
- package/styles/fonts/roboto/roboto-v30-latin-regular.woff2 +0 -0
- package/styles/fonts/roboto-font.scss +109 -0
- package/styles/style.scss +54 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { NavbarConfig } from '../../models';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'lf-navbar',
|
|
6
|
+
templateUrl: './navbar.component.html',
|
|
7
|
+
styleUrls: ['./navbar.component.scss'],
|
|
8
|
+
standalone: false
|
|
9
|
+
})
|
|
10
|
+
export class NavbarComponent {
|
|
11
|
+
|
|
12
|
+
@Input() public navbarConfig: NavbarConfig | null = null;
|
|
13
|
+
@Input() public isMobile: boolean = false;
|
|
14
|
+
@Input() public currentRoute: string;
|
|
15
|
+
|
|
16
|
+
@Output() public navbarButtonClickEvent = new EventEmitter<string>();
|
|
17
|
+
|
|
18
|
+
public buttonClicked(id: string): void {
|
|
19
|
+
if(!id) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.navbarButtonClickEvent.emit(id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public isActive(id: string): boolean {
|
|
27
|
+
if(!id || !this.currentRoute) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const route: string = this.currentRoute.substring(this.currentRoute.indexOf('/') + 1);
|
|
32
|
+
return route === id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<!-- header-height: 64px; navbar-size: 80px; floating-button-bottom: 24px -->
|
|
2
|
+
|
|
3
|
+
<div class="lf-scaffold mat-app-background mat-typography" [ngClass]="scaffoldConfig?.class">
|
|
4
|
+
<!-- loading overlay -->
|
|
5
|
+
@if (scaffoldConfig?.loading) {
|
|
6
|
+
<lf-loading-overlay></lf-loading-overlay>
|
|
7
|
+
}
|
|
8
|
+
<!-- header -->
|
|
9
|
+
<lf-header
|
|
10
|
+
[headerConfig]="headerConfig"
|
|
11
|
+
[isMobile]="isMobile"
|
|
12
|
+
[routeLoading]="routeLoading"
|
|
13
|
+
[currentRoute]="currentRoute"
|
|
14
|
+
(headerButtonClickEvent)="headerButtonClicked($event)"
|
|
15
|
+
(headerInputSubmitEvent)="headerInputSubmitted($event)"
|
|
16
|
+
(headerInputChangeEvent)="headerInputChanged($event)"></lf-header>
|
|
17
|
+
<!-- navbar -->
|
|
18
|
+
<lf-navbar
|
|
19
|
+
[navbarConfig]="navbarConfig"
|
|
20
|
+
[isMobile]="isMobile"
|
|
21
|
+
[currentRoute]="currentRoute"
|
|
22
|
+
(navbarButtonClickEvent)="navbarButtonClicked($event)"></lf-navbar>
|
|
23
|
+
<!-- content wrapper -->
|
|
24
|
+
<div
|
|
25
|
+
class="lf-content-wrapper"
|
|
26
|
+
[class.lf-show-navbar]="navbarConfig?.enable && !isMobile"
|
|
27
|
+
[class.lf-show-header]="headerConfig?.enable && !(navbarConfig?.enable && isMobile)"
|
|
28
|
+
[class.lf-show-navbar-mobile]="navbarConfig?.enable && isMobile && !headerConfig?.enable"
|
|
29
|
+
[class.lf-show-header-and-navbar-mobile]="
|
|
30
|
+
headerConfig?.enable && navbarConfig?.enable && isMobile
|
|
31
|
+
"
|
|
32
|
+
#scrollContainer
|
|
33
|
+
cdkScrollable>
|
|
34
|
+
<!-- drawer -->
|
|
35
|
+
<lf-drawer
|
|
36
|
+
[drawerConfig]="drawerConfig"
|
|
37
|
+
[drawerPortal]="drawerPortal"
|
|
38
|
+
[isMobile]="isMobile"
|
|
39
|
+
[fixedOffset]="headerConfig?.enable ? 64 : 0">
|
|
40
|
+
<!-- drawer content -->
|
|
41
|
+
<ng-content select="[drawerContent]" drawerContent></ng-content>
|
|
42
|
+
<!-- content title card -->
|
|
43
|
+
<lf-content-title-card
|
|
44
|
+
[contentTitleCardConfig]="contentTitleCardConfig"
|
|
45
|
+
[isMobile]="isMobile"
|
|
46
|
+
[routeHistory]="routeHistory"
|
|
47
|
+
(backButtonClickEvent)="backButtonClicked()"></lf-content-title-card>
|
|
48
|
+
<!-- main content -->
|
|
49
|
+
<div
|
|
50
|
+
class="lf-content"
|
|
51
|
+
[class.lf-content-mobile]="isMobile"
|
|
52
|
+
[class.lf-show-footer]="footerConfig?.enable"
|
|
53
|
+
#content>
|
|
54
|
+
<ng-content></ng-content>
|
|
55
|
+
</div>
|
|
56
|
+
<!-- footer -->
|
|
57
|
+
<lf-footer [footerConfig]="footerConfig"></lf-footer>
|
|
58
|
+
</lf-drawer>
|
|
59
|
+
</div>
|
|
60
|
+
<!-- to top button -->
|
|
61
|
+
<lf-floating-button
|
|
62
|
+
[floatingButtonConfig]="floatingButtonConfig"
|
|
63
|
+
[onTop]="scrollTopPosition <= 0"
|
|
64
|
+
[isMobile]="isMobile && navbarConfig?.enable!"
|
|
65
|
+
[bottomBarEnabled]="bottomBarConfig?.enable!"
|
|
66
|
+
(floatingButtonClickEvent)="floatingButtonClicked($event)"></lf-floating-button>
|
|
67
|
+
<!-- bottom bar -->
|
|
68
|
+
<lf-bottom-bar
|
|
69
|
+
[bottomBarConfig]="bottomBarConfig"
|
|
70
|
+
[isMobile]="isMobile && navbarConfig?.enable!"
|
|
71
|
+
[navbarEnabled]="navbarConfig?.enable!"
|
|
72
|
+
(bottomBarCloseClickEvent)="bottomBarCloseClicked($event)"
|
|
73
|
+
(bottomBarButtonClickEvent)="bottomBarButtonClicked($event)"></lf-bottom-bar>
|
|
74
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@use '../../../../styles/variables' as *;
|
|
2
|
+
|
|
3
|
+
.lf-scaffold {
|
|
4
|
+
height: 100vh;
|
|
5
|
+
|
|
6
|
+
.lf-content-wrapper {
|
|
7
|
+
transition: height $transition;
|
|
8
|
+
position: absolute;
|
|
9
|
+
top: 0;
|
|
10
|
+
right: 0;
|
|
11
|
+
bottom: 0;
|
|
12
|
+
left: 0;
|
|
13
|
+
overflow-y: auto;
|
|
14
|
+
|
|
15
|
+
&.lf-show-navbar {
|
|
16
|
+
left: calc($navbar-height + (2 * $navbar-padding));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&.lf-show-header {
|
|
20
|
+
top: $header-height;
|
|
21
|
+
height: calc(100vh - $header-height);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&.lf-show-navbar-mobile {
|
|
25
|
+
height: calc(100vh - $navbar-height-mobile);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&.lf-show-header-and-navbar-mobile {
|
|
29
|
+
top: $header-height;
|
|
30
|
+
height: calc(100vh - $header-height - $navbar-height-mobile);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.lf-content {
|
|
34
|
+
padding-left: $content-padding;
|
|
35
|
+
padding-right: $content-padding;
|
|
36
|
+
|
|
37
|
+
&.lf-content-mobile {
|
|
38
|
+
padding-left: $content-padding-mobile;
|
|
39
|
+
padding-right: $content-padding-mobile;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.lf-show-footer{
|
|
43
|
+
min-height: 100%;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { DebugElement } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
import { By } from '@angular/platform-browser';
|
|
4
|
+
import { RouterTestingModule } from '@angular/router/testing';
|
|
5
|
+
import { Logger } from '../../services';
|
|
6
|
+
import { SharedModule } from '../../shared/shared.module';
|
|
7
|
+
import { BottomBarComponent } from '../bottom-bar/bottom-bar.component';
|
|
8
|
+
import { ContentTitleCardComponent } from '../content-title-card/content-title-card.component';
|
|
9
|
+
import { DrawerComponent } from '../drawer/drawer.component';
|
|
10
|
+
import { FloatingButtonComponent } from '../floating-button/floating-button.component';
|
|
11
|
+
import { FooterComponent } from '../footer/footer.component';
|
|
12
|
+
import { HeaderComponent } from '../header/header.component';
|
|
13
|
+
import { LoadingOverlayComponent } from '../loading-overlay/loading-overlay.component';
|
|
14
|
+
import { NavbarComponent } from '../navbar/navbar.component';
|
|
15
|
+
import { ScaffoldComponent } from './scaffold.component';
|
|
16
|
+
|
|
17
|
+
// @Component({
|
|
18
|
+
// selector: 'lf-header',
|
|
19
|
+
// template: ''
|
|
20
|
+
// })
|
|
21
|
+
// class MockHeaderComponent {
|
|
22
|
+
// @Input() public headerConfig: HeaderConfig = {};
|
|
23
|
+
// @Input() public isMobile: boolean = false;
|
|
24
|
+
// @Input() public routeLoading: boolean = false;
|
|
25
|
+
// }
|
|
26
|
+
|
|
27
|
+
class MockLogger { }
|
|
28
|
+
|
|
29
|
+
describe('ScaffoldComponent', () => {
|
|
30
|
+
let component: ScaffoldComponent;
|
|
31
|
+
let fixture: ComponentFixture<ScaffoldComponent>;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
await TestBed.configureTestingModule({
|
|
35
|
+
declarations: [
|
|
36
|
+
// MockHeaderComponent
|
|
37
|
+
ScaffoldComponent,
|
|
38
|
+
LoadingOverlayComponent,
|
|
39
|
+
HeaderComponent,
|
|
40
|
+
NavbarComponent,
|
|
41
|
+
DrawerComponent,
|
|
42
|
+
ContentTitleCardComponent,
|
|
43
|
+
FooterComponent,
|
|
44
|
+
FloatingButtonComponent,
|
|
45
|
+
BottomBarComponent
|
|
46
|
+
],
|
|
47
|
+
imports: [
|
|
48
|
+
SharedModule,
|
|
49
|
+
RouterTestingModule
|
|
50
|
+
],
|
|
51
|
+
providers: [
|
|
52
|
+
{ provide: Logger, useClass: MockLogger }
|
|
53
|
+
]
|
|
54
|
+
}).compileComponents();
|
|
55
|
+
|
|
56
|
+
fixture = TestBed.createComponent(ScaffoldComponent);
|
|
57
|
+
component = fixture.componentInstance;
|
|
58
|
+
fixture.detectChanges();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should create', () => {
|
|
62
|
+
expect(component).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should render the header component', () => {
|
|
66
|
+
component.headerConfig = { enable: true };
|
|
67
|
+
fixture.detectChanges();
|
|
68
|
+
const headerDebugElement: DebugElement = fixture.debugElement.query(
|
|
69
|
+
By.directive(HeaderComponent)
|
|
70
|
+
);
|
|
71
|
+
expect(headerDebugElement).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
it('should render the navbar component', () => {
|
|
76
|
+
component.navbarConfig = { enable: true };
|
|
77
|
+
fixture.detectChanges();
|
|
78
|
+
const navbarDebugElement: DebugElement = fixture.debugElement.query(
|
|
79
|
+
By.directive(NavbarComponent)
|
|
80
|
+
);
|
|
81
|
+
expect(navbarDebugElement).toBeTruthy();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should render the drawer component', () => {
|
|
85
|
+
component.drawerConfig = { enable: true };
|
|
86
|
+
fixture.detectChanges();
|
|
87
|
+
const drawerDebugElement: DebugElement = fixture.debugElement.query(
|
|
88
|
+
By.directive(DrawerComponent)
|
|
89
|
+
);
|
|
90
|
+
expect(drawerDebugElement).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should render the title component', () => {
|
|
94
|
+
component.contentTitleCardConfig = { enable: true };
|
|
95
|
+
fixture.detectChanges();
|
|
96
|
+
const titleDebugElement: DebugElement = fixture.debugElement.query(
|
|
97
|
+
By.directive(ContentTitleCardComponent)
|
|
98
|
+
);
|
|
99
|
+
expect(titleDebugElement).toBeTruthy();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should render the footer component', () => {
|
|
103
|
+
component.footerConfig = { enable: true };
|
|
104
|
+
fixture.detectChanges();
|
|
105
|
+
const footerDebugElement: DebugElement = fixture.debugElement.query(
|
|
106
|
+
By.directive(FooterComponent)
|
|
107
|
+
);
|
|
108
|
+
expect(footerDebugElement).toBeTruthy();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should render the button component', () => {
|
|
112
|
+
component.floatingButtonConfig = { enable: true };
|
|
113
|
+
fixture.detectChanges();
|
|
114
|
+
const buttonDebugElement: DebugElement = fixture.debugElement.query(
|
|
115
|
+
By.directive(FloatingButtonComponent)
|
|
116
|
+
);
|
|
117
|
+
expect(buttonDebugElement).toBeTruthy();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Breakpoints, BreakpointState } from '@angular/cdk/layout';
|
|
2
|
+
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
|
|
3
|
+
|
|
4
|
+
import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild, DOCUMENT, inject } from '@angular/core';
|
|
5
|
+
import { ActivatedRoute } from '@angular/router';
|
|
6
|
+
import { debounceTime, distinctUntilChanged, fromEvent, Subscription } from 'rxjs';
|
|
7
|
+
import { BottomBarConfig, ContentTitleCardConfig, DrawerConfig, FloatingButtonConfig, FooterConfig, HeaderConfig, ScaffoldLibraryConfig, NavbarConfig, ScaffoldConfig } from '../../models';
|
|
8
|
+
import { CONFIG } from '../../scaffold.module';
|
|
9
|
+
import { BreakpointService, Logger, RouterService, ScaffoldService } from '../../services';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'lf-scaffold',
|
|
13
|
+
templateUrl: './scaffold.component.html',
|
|
14
|
+
styleUrls: ['./scaffold.component.scss'],
|
|
15
|
+
standalone: false
|
|
16
|
+
})
|
|
17
|
+
export class ScaffoldComponent implements OnInit, OnDestroy {
|
|
18
|
+
private scaffoldService = inject(ScaffoldService);
|
|
19
|
+
private breakpointService = inject(BreakpointService);
|
|
20
|
+
private routerService = inject(RouterService);
|
|
21
|
+
private logger = inject(Logger);
|
|
22
|
+
private route = inject(ActivatedRoute);
|
|
23
|
+
private document = inject<Document>(DOCUMENT);
|
|
24
|
+
private config = inject<ScaffoldLibraryConfig>(CONFIG, { optional: true });
|
|
25
|
+
|
|
26
|
+
@ViewChild('scrollContainer', { static: true }) public scrollContainer: ElementRef;
|
|
27
|
+
@ViewChild('content', { static: true }) public content: ElementRef;
|
|
28
|
+
|
|
29
|
+
@Output() public headerButtonClickEvent = new EventEmitter<string>();
|
|
30
|
+
@Output() public headerInputSubmitEvent = new EventEmitter<string>();
|
|
31
|
+
@Output() public headerInputChangeEvent = new EventEmitter<string>();
|
|
32
|
+
@Output() public navbarButtonClickEvent = new EventEmitter<string>();
|
|
33
|
+
@Output() public floatingButtonClickEvent = new EventEmitter<string>();
|
|
34
|
+
@Output() public bottomBarButtonClickEvent = new EventEmitter<string>();
|
|
35
|
+
|
|
36
|
+
public scaffoldConfig: ScaffoldConfig | null = null;
|
|
37
|
+
public headerConfig: HeaderConfig | null = null;
|
|
38
|
+
public navbarConfig: NavbarConfig | null = null;
|
|
39
|
+
public drawerConfig: DrawerConfig | null = null;
|
|
40
|
+
public drawerPortal: ComponentPortal<unknown> | TemplatePortal<unknown> | null;
|
|
41
|
+
public footerConfig: FooterConfig | null = null;
|
|
42
|
+
public contentTitleCardConfig: ContentTitleCardConfig | null = null;
|
|
43
|
+
public floatingButtonConfig: FloatingButtonConfig | null = null;
|
|
44
|
+
public bottomBarConfig: BottomBarConfig | null = null;
|
|
45
|
+
|
|
46
|
+
public routeHistory: string[] = [];
|
|
47
|
+
public currentRoute: string;
|
|
48
|
+
public isMobile: boolean = false;
|
|
49
|
+
public routeLoading: boolean = false;
|
|
50
|
+
public scrollTopPosition: number = 0;
|
|
51
|
+
|
|
52
|
+
private _subscription: Subscription = new Subscription;
|
|
53
|
+
|
|
54
|
+
ngOnInit(): void {
|
|
55
|
+
// Listen for config changes
|
|
56
|
+
this._subscription.add(this.scaffoldService.scaffoldConfig$.subscribe((scaffoldConfig: ScaffoldConfig) => {
|
|
57
|
+
if (this.config?.debugging) this.logger.log('[ScaffoldConfig]', scaffoldConfig);
|
|
58
|
+
|
|
59
|
+
this.scaffoldConfig = scaffoldConfig;
|
|
60
|
+
this.headerConfig = this.scaffoldConfig.headerConfig!;
|
|
61
|
+
this.navbarConfig = this.scaffoldConfig.navbarConfig!;
|
|
62
|
+
this.drawerConfig = this.scaffoldConfig.drawerConfig!;
|
|
63
|
+
this.footerConfig = this.scaffoldConfig.footerConfig!;
|
|
64
|
+
this.contentTitleCardConfig = this.scaffoldConfig.contentTitleCardConfig!;
|
|
65
|
+
this.floatingButtonConfig = this.scaffoldConfig.floatingButtonConfig!;
|
|
66
|
+
this.bottomBarConfig = this.scaffoldConfig.bottomBarConfig!;
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
// Listen for drawer portal changes
|
|
70
|
+
this._subscription.add(this.scaffoldService.drawerPortal$.subscribe((drawerPortal: ComponentPortal<unknown> | TemplatePortal<unknown> | null) => {
|
|
71
|
+
if (this.config?.debugging) this.logger.log('[DrawerPortal]', drawerPortal);
|
|
72
|
+
|
|
73
|
+
this.drawerPortal = drawerPortal;
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// Listen for breakpoint changes
|
|
77
|
+
this._subscription.add(this.breakpointService.breakpoint$.subscribe((breakpointState: BreakpointState) => {
|
|
78
|
+
if (this.config?.debugging) this.logger.log('[BreakpointState]', breakpointState);
|
|
79
|
+
|
|
80
|
+
if (breakpointState.breakpoints[Breakpoints.XSmall]) {
|
|
81
|
+
this.isMobile = true;
|
|
82
|
+
} else if (breakpointState.breakpoints[Breakpoints.Small]) {
|
|
83
|
+
this.isMobile = true;
|
|
84
|
+
} else if (breakpointState.breakpoints[Breakpoints.Medium]) {
|
|
85
|
+
this.isMobile = false;
|
|
86
|
+
} else if (breakpointState.breakpoints[Breakpoints.Large]) {
|
|
87
|
+
this.isMobile = false;
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Listen for route changes
|
|
92
|
+
this._subscription.add(this.routerService.routeHistory$.subscribe((routeHistory: string[]) => {
|
|
93
|
+
if (this.config?.debugging) this.logger.log('[RouteHistory]', routeHistory);
|
|
94
|
+
|
|
95
|
+
if (routeHistory) {
|
|
96
|
+
this.routeHistory = routeHistory;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.scrollContainer && this.scaffoldConfig?.scrollPositionRestoration) {
|
|
100
|
+
this.scrollContainer.nativeElement.scrollTop = 0;
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Listen for current route changes
|
|
105
|
+
this._subscription.add(this.routerService.currentRoute$.subscribe((currentRout: string) => {
|
|
106
|
+
this.currentRoute = currentRout
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// Listen for route loading
|
|
110
|
+
this._subscription.add(this.routerService.loading$.subscribe((routeLoading: boolean) => {
|
|
111
|
+
this.routeLoading = routeLoading
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
// Listen to scroll events
|
|
115
|
+
if (this.scrollContainer) {
|
|
116
|
+
const element: HTMLElement = this.scrollContainer.nativeElement;
|
|
117
|
+
|
|
118
|
+
this._subscription.add(fromEvent(element, 'scroll').pipe(
|
|
119
|
+
distinctUntilChanged(),
|
|
120
|
+
debounceTime(100)
|
|
121
|
+
).subscribe((e: Event) => {
|
|
122
|
+
const target: HTMLElement = e.target as HTMLElement;
|
|
123
|
+
this.scrollTopPosition = target.scrollTop;
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Listen for fragments in the current route
|
|
128
|
+
if (this.scaffoldConfig?.anchorScrolling) {
|
|
129
|
+
this._subscription.add(this.route.fragment.subscribe((fragment: string | null) => {
|
|
130
|
+
if (fragment) {
|
|
131
|
+
if (this.config?.debugging) this.logger.log('[RouteFragment]', fragment);
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
const element = this.document.querySelector(`#${fragment}`);
|
|
134
|
+
if (element) {
|
|
135
|
+
element.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'nearest' });
|
|
136
|
+
}
|
|
137
|
+
}, 100);
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ngOnDestroy(): void {
|
|
144
|
+
if (this._subscription) {
|
|
145
|
+
this._subscription.unsubscribe();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public headerButtonClicked(id: string): void {
|
|
150
|
+
this.scaffoldService.buttonClickEventValue = id;
|
|
151
|
+
this.headerButtonClickEvent.emit(id);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public headerInputSubmitted(value: string): void {
|
|
155
|
+
this.headerInputSubmitEvent.emit(value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public headerInputChanged(value: string): void {
|
|
159
|
+
this.scaffoldService.headerInputChangeValue = value;
|
|
160
|
+
this.headerInputChangeEvent.emit(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public navbarButtonClicked(id: string): void {
|
|
164
|
+
this.scaffoldService.buttonClickEventValue = id;
|
|
165
|
+
this.navbarButtonClickEvent.emit(id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public backButtonClicked(): void {
|
|
169
|
+
this.routerService.navigateBack();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public floatingButtonClicked(id: string): void {
|
|
173
|
+
if (!id && this.scrollContainer) {
|
|
174
|
+
this.scrollContainer.nativeElement.scrollTop = 0;
|
|
175
|
+
} else {
|
|
176
|
+
this.scaffoldService.buttonClickEventValue = id;
|
|
177
|
+
this.floatingButtonClickEvent.emit(id);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public bottomBarCloseClicked(id: string): void {
|
|
182
|
+
this.scaffoldService.buttonClickEventValue = id;
|
|
183
|
+
this.bottomBarButtonClickEvent.emit(id);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public bottomBarButtonClicked(id: string): void {
|
|
187
|
+
this.scaffoldService.buttonClickEventValue = id;
|
|
188
|
+
this.bottomBarButtonClickEvent.emit(id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HttpEvent,
|
|
3
|
+
HttpHandler,
|
|
4
|
+
HttpInterceptor,
|
|
5
|
+
HttpRequest
|
|
6
|
+
} from '@angular/common/http';
|
|
7
|
+
import { Injectable, inject } from '@angular/core';
|
|
8
|
+
import { Observable, finalize } from 'rxjs';
|
|
9
|
+
import { ScaffoldService } from '../services';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class LoadingInterceptor implements HttpInterceptor {
|
|
13
|
+
private scaffoldService = inject(ScaffoldService);
|
|
14
|
+
|
|
15
|
+
private activeRequests = 0;
|
|
16
|
+
private loadingDelay = 100; // milliseconds
|
|
17
|
+
private spinnerTimeout: any;
|
|
18
|
+
|
|
19
|
+
public intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
|
20
|
+
this.activeRequests++;
|
|
21
|
+
|
|
22
|
+
// Set a delayed spinner timeout
|
|
23
|
+
if (this.activeRequests === 1) {
|
|
24
|
+
this.spinnerTimeout = setTimeout(() => {
|
|
25
|
+
this.scaffoldService.updateScaffoldProperty('loading', true);
|
|
26
|
+
}, this.loadingDelay);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return next.handle(req).pipe(
|
|
30
|
+
// tap({
|
|
31
|
+
// next: (event) => {
|
|
32
|
+
// if (event instanceof HttpResponse) {
|
|
33
|
+
// // Successful response
|
|
34
|
+
// }
|
|
35
|
+
// },
|
|
36
|
+
// error: (error: HttpErrorResponse) => {
|
|
37
|
+
// // Handle error if needed
|
|
38
|
+
// }
|
|
39
|
+
// }),
|
|
40
|
+
finalize(() => {
|
|
41
|
+
this.activeRequests--;
|
|
42
|
+
|
|
43
|
+
if (this.activeRequests === 0) {
|
|
44
|
+
// Clear any pending spinner timeout
|
|
45
|
+
clearTimeout(this.spinnerTimeout);
|
|
46
|
+
this.scaffoldService.updateScaffoldProperty('loading', false);
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface FloatingButtonConfig {
|
|
2
|
+
enable?: boolean;
|
|
3
|
+
id?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
matIcon?: string;
|
|
6
|
+
svgIcon?: string;
|
|
7
|
+
outlineIcon?: boolean;
|
|
8
|
+
tooltip?: string;
|
|
9
|
+
horizontalPosition?: 'left' | 'center' | 'right';
|
|
10
|
+
bottomPositionPx?: number;
|
|
11
|
+
autoHide?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MenuButton } from './menu-button.model';
|
|
2
|
+
|
|
3
|
+
export interface HeaderConfig {
|
|
4
|
+
enable?: boolean;
|
|
5
|
+
svgLogo?: string;
|
|
6
|
+
imgLogo?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
titleRouterLink?: string;
|
|
10
|
+
loading?: boolean;
|
|
11
|
+
showRouteLoading?: boolean;
|
|
12
|
+
leftMenuButton?: MenuButton;
|
|
13
|
+
rightMenuButtons?: MenuButton[];
|
|
14
|
+
inputConfig?: HeaderInputConfig;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HeaderInputConfig {
|
|
19
|
+
enable?: boolean;
|
|
20
|
+
label?: string;
|
|
21
|
+
matIconSubmit?: string;
|
|
22
|
+
// matIconPrefix?: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
autoFocus?: boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BottomBarConfig } from './bottom-bar-config.model';
|
|
2
|
+
export { ConfirmDialogConfig } from './confirm-dialog-config.model';
|
|
3
|
+
export { ContentTitleCardConfig } from './content-title-card-config.model';
|
|
4
|
+
export { DrawerConfig } from './drawer-config.model';
|
|
5
|
+
export { FloatingButtonConfig } from './floating-button-config.model';
|
|
6
|
+
export { FooterConfig } from './footer-config.model';
|
|
7
|
+
export { HeaderConfig, HeaderInputConfig } from './header-config.model';
|
|
8
|
+
export { ScaffoldLibraryConfig } from './library-config.model';
|
|
9
|
+
export { MenuButton } from './menu-button.model';
|
|
10
|
+
export { NavbarConfig } from './navbar-config.model';
|
|
11
|
+
export { NavigationLink } from './navigation-link.model';
|
|
12
|
+
export { PlaceholderConfig } from './placeholder-config.model';
|
|
13
|
+
export { ScaffoldConfig } from './scaffold-config.model';
|
|
14
|
+
export { SeoConfig } from './seo-config.model';
|
|
15
|
+
|