fix duplicate singleton

This commit is contained in:
rubikscraft
2022-03-18 20:45:30 +01:00
parent 460c699481
commit e4bce27c4f
20 changed files with 198 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
import { BreakpointObserver } from '@angular/cdk/layout';
import { ComponentPortal, Portal } from '@angular/cdk/portal';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Portal } from '@angular/cdk/portal';
import { Component, Injector, OnInit, ViewChild } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import {
ActivatedRoute,
@@ -30,7 +30,8 @@ export class AppComponent implements OnInit {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private breakPointObserver: BreakpointObserver
private breakPointObserver: BreakpointObserver,
private injector: Injector
) {}
private get routeData(): PRouteData {
@@ -88,10 +89,8 @@ export class AppComponent implements OnInit {
console.log(data);
if (data.sidebar !== undefined) {
this.sidebarPortal?.detach();
this.sidebarPortal = new ComponentPortal(data.sidebar);
if (data._sidebar_portal !== undefined) {
this.sidebarPortal = data._sidebar_portal;
this.hasSidebar = true;
} else {
this.hasSidebar = false;

View File

@@ -1,12 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ApiModule } from 'src/app/services/api/api.module';
import { FooterComponent } from './footer.component';
@NgModule({
declarations: [FooterComponent],
imports: [CommonModule, ApiModule],
imports: [CommonModule],
exports: [FooterComponent],
})
export class FooterModule {}

View File

@@ -5,7 +5,6 @@ import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterModule } from '@angular/router';
import { ApiModule } from 'src/app/services/api/api.module';
import { UtilModule } from 'src/app/util/util.module';
import { HeaderComponent } from './header.component';
@@ -15,7 +14,6 @@ import { HeaderComponent } from './header.component';
MatToolbarModule,
MatButtonModule,
RouterModule,
ApiModule,
MatIconModule,
MatMenuModule,
UtilModule,

View File

@@ -1,10 +1,9 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ApiModule } from '../services/api/api.module';
import { PermissionGuard } from './permission.guard';
@NgModule({
imports: [CommonModule, ApiModule],
imports: [CommonModule],
providers: [PermissionGuard],
exports: [],
})

View File

@@ -2,24 +2,39 @@ import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot
} from '@angular/router';
import { Permissions } from 'picsur-shared/dist/dto/permissions';
import { isPermissionsArray } from 'picsur-shared/dist/util/permissions';
import { PRouteData } from '../models/picsur-routes';
import { PermissionService } from '../services/api/permission.service';
@Injectable({
providedIn: 'root',
})
export class PermissionGuard implements CanActivate {
export class PermissionGuard implements CanActivate, CanActivateChild {
constructor(
private permissionService: PermissionService,
private router: Router
) {}
async canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) {
console.log('canActivateChild');
return await this.can(childRoute, state);
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const requiredPermissions: Permissions = route.data['permissions'];
console.log('canActivate');
return await this.can(route, state);
}
private async can(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const requiredPermissions: Permissions = this.nestedPermissions(route);
if (!isPermissionsArray(requiredPermissions)) {
throw new Error(
`PermissionGuard: route data 'permissions' must be an array of Permission values`
@@ -37,4 +52,19 @@ export class PermissionGuard implements CanActivate {
}
return isOk;
}
private nestedPermissions(route: ActivatedRouteSnapshot): Permissions {
const data: PRouteData = route.data;
let permissions: Permissions = [];
if (data?.permissions) {
permissions = permissions.concat(data.permissions);
}
if (route.firstChild) {
permissions = permissions.concat(
this.nestedPermissions(route.firstChild)
);
}
return permissions;
}
}

View File

@@ -1,7 +1,7 @@
import { ComponentType, Portal } from '@angular/cdk/portal';
import { Route } from '@angular/router';
import { Permissions } from 'picsur-shared/dist/dto/permissions';
export type PRouteData = {
page?: {
title?: string;
@@ -10,7 +10,8 @@ export type PRouteData = {
};
permissions?: Permissions;
noContainer?: boolean;
sidebar?: string;
sidebar?: ComponentType<unknown>;
_sidebar_portal?: Portal<unknown>;
};
export type PRoute = Route & {

View File

@@ -1,10 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { PermissionService } from 'src/app/services/api/permission.service';
@Component({
templateUrl: './settings-general.component.html',
})
export class SettingsGeneralComponent implements OnInit {
constructor() {}
constructor(private permissionsService: PermissionService) {}
ngOnInit(): void {}
ngOnInit(): void {
this.subscribePermissions();
}
@AutoUnsubscribe()
subscribePermissions() {
return this.permissionsService.live.subscribe((permissions) => {
console.log('Pogogog', permissions);
});
}
}

View File

@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SettingsGeneralComponent } from './settings-general.component';
import { SettingsGeneralRoutingModule } from './settings-home.routing.module';
import { SettingsGeneralRoutingModule } from './settings-general.routing.module';
@NgModule({
declarations: [SettingsGeneralComponent],

View File

@@ -1,5 +1,4 @@
<mat-nav-list>
<span mat-subheader>Big Peepee</span>
<ng-container *ngIf="personalRoutes.length > 0">
<span mat-subheader>Personal</span>
<a

View File

@@ -1,6 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { PRoutes } from 'src/app/models/picsur-routes';
import { PermissionService } from 'src/app/services/api/permission.service';
@@ -8,32 +6,30 @@ import { PermissionService } from 'src/app/services/api/permission.service';
templateUrl: './settings-sidebar.component.html',
styleUrls: ['./settings-sidebar.component.scss'],
})
export class SettingsSidebarComponent implements OnInit {
export class SettingsSidebarComponent implements OnInit, OnDestroy {
//private settingsRoutes: PRoutes = [];
private accessibleRoutes: PRoutes = [];
private settingsRoutes: PRoutes = [];
personalRoutes: PRoutes = [];
systemRoutes: PRoutes = [];
constructor(
/* @Inject('SettingsRoutes')*/
private permissionService: PermissionService,
private router: Router
) {
console.error("contstruct");
console.log('stat', this.router.getCurrentNavigation());
@Inject('SettingsRoutes') private settingsRoutes: PRoutes,
private permissionService: PermissionService
) {}
ngOnDestroy(): void {
console.error('destoryed');
}
ngOnInit() {
console.log('SettingsSidebarComponent.ngOnInit()');
console.log('SettingsSidebarComponent.ngOnInit() with ' + this.permissionService.counter);
this.subscribePermissions();
}
@AutoUnsubscribe()
// @AutoUnsubscribe()
private subscribePermissions() {
return this.permissionService.live.subscribe((permissions) => {
const o = this.permissionService.live.subscribe((permissions) => {
console.warn('pog', permissions);
this.accessibleRoutes = this.settingsRoutes
.filter((route) => route.path !== '')
.filter((route) =>
@@ -51,5 +47,9 @@ export class SettingsSidebarComponent implements OnInit {
(route) => route.data?.page?.category === 'system'
);
});
o.add(() => {
console.error('stopped');
});
return o;
}
}

View File

@@ -0,0 +1,2 @@
<h1>Settings Syspref</h1>

View File

@@ -0,0 +1,10 @@
import { Component, OnInit } from '@angular/core';
@Component({
templateUrl: './settings-syspref.component.html',
})
export class SettingsSysprefComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}

View File

@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SettingsSysprefComponent } from './settings-syspref.component';
import { SettingsSysprefRoutingModule } from './settings-syspref.routing.module';
@NgModule({
declarations: [SettingsSysprefComponent],
imports: [
CommonModule,
SettingsSysprefRoutingModule,
],
})
export class SettingsSysprefRouteModule {}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { PRoutes } from 'src/app/models/picsur-routes';
import { SettingsSysprefComponent } from './settings-syspref.component';
const routes: PRoutes = [
{
path: '',
component: SettingsSysprefComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SettingsSysprefRoutingModule {}

View File

@@ -1,8 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { Injector, NgModule } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { ApiModule } from 'src/app/services/api/api.module';
import { SettingsSidebarComponent } from './settings-sidebar/settings-sidebar.component';
import { SettingsRoutingModule } from './settings.routing.module';
@@ -11,9 +10,11 @@ import { SettingsRoutingModule } from './settings.routing.module';
imports: [
CommonModule,
SettingsRoutingModule.forRoot(),
ApiModule,
MatListModule,
MatIconModule,
],
exports: [SettingsRoutingModule],
})
export class SettingsRouteModule {}
export class SettingsRouteModule {
constructor(private injector: Injector) {}
}

View File

@@ -3,30 +3,50 @@ import { RouterModule } from '@angular/router';
import { Permission } from 'picsur-shared/dist/dto/permissions';
import { PermissionGuard } from 'src/app/guards/permission.guard';
import { PRoutes } from 'src/app/models/picsur-routes';
import { SettingsGeneralRouteModule } from './settings-general/settings-home.module';
import { SidebarResolverService } from 'src/app/services/sidebar-resolver/sidebar-resolver.service';
import { SettingsGeneralRouteModule } from './settings-general/settings-general.module';
import { SettingsSidebarComponent } from './settings-sidebar/settings-sidebar.component';
import { SettingsSysprefRouteModule } from './settings-syspref/settings-syspref.module';
const SettingsRoutes: PRoutes = [
{
path: '',
redirectTo: 'general',
},
{
path: 'general',
loadChildren: () => SettingsGeneralRouteModule,
canActivate: [PermissionGuard],
data: {
permissions: [Permission.Settings],
page: {
title: 'General',
icon: 'settings',
category: 'personal',
children: [
{
path: '',
redirectTo: 'general',
},
{
path: 'general',
loadChildren: () => SettingsGeneralRouteModule,
data: {
permissions: [Permission.Settings],
page: {
title: 'General',
icon: 'settings',
category: 'personal',
},
},
},
{
path: 'general',
loadChildren: () => SettingsSysprefRouteModule,
data: {
permissions: [Permission.SysPrefManage],
page: {
title: 'Sys Preferences',
icon: 'settings',
category: 'system',
},
},
},
],
canActivate: [PermissionGuard],
canActivateChild: [PermissionGuard],
data: {
sidebar: SettingsSidebarComponent,
},
},
{
path: 'sidebar',
component: SettingsSidebarComponent,
resolve: SidebarResolverService.build(),
},
];
@@ -41,7 +61,7 @@ export class SettingsRoutingModule {
providers: [
{
provide: 'SettingsRoutes',
useFactory: () => SettingsRoutes,
useFactory: () => SettingsRoutes[0].children,
},
],
};

View File

@@ -1,21 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ApiService } from './api.service';
import { ImageService } from './image.service';
import { InfoService } from './info.service';
import { KeyService } from './key.service';
import { PermissionService } from './permission.service';
import { UserService } from './user.service';
@NgModule({
providers: [
ApiService,
ImageService,
UserService,
PermissionService,
KeyService,
InfoService,
],
imports: [CommonModule],
})
export class ApiModule {}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, Optional, SkipSelf } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { UserMePermissionsResponse } from 'picsur-shared/dist/dto/api/user.dto';
import {
@@ -10,11 +10,20 @@ import { BehaviorSubject, filter, map, Observable, take } from 'rxjs';
import { ApiService } from './api.service';
import { UserService } from './user.service';
@Injectable()
let i = 0;
@Injectable({ providedIn: 'root' })
export class PermissionService {
private readonly logger = console;
public counter = 0;
constructor(private userService: UserService, private api: ApiService) {
constructor(
private userService: UserService,
private api: ApiService,
@Optional() @SkipSelf() parent?: PermissionService
) {
this.counter = ++i;
console.log('PermissionService.constructor(' + this.counter + ')');
this.onUser();
}
@@ -45,12 +54,13 @@ export class PermissionService {
@AutoUnsubscribe()
private onUser() {
return this.userService.live.subscribe(async (user) => {
console.log('PermissionService.onUser(' + this.counter + ')', user);
const permissions = await this.fetchPermissions();
if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason());
return;
}
console.log('Permissions next', permissions);
this.permissionsSubject.next(permissions);
});
}

View File

@@ -0,0 +1,26 @@
import { ComponentPortal, Portal } from '@angular/cdk/portal';
import { Injectable, Injector } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { PRouteData } from 'src/app/models/picsur-routes';
@Injectable({
providedIn: 'any',
})
export class SidebarResolverService
implements Resolve<Portal<unknown> | undefined>
{
constructor(private injector: Injector) {}
resolve(route: ActivatedRouteSnapshot) {
const data: PRouteData = route.data;
if (!data.sidebar) return undefined;
return new ComponentPortal(data.sidebar, null, this.injector);
}
static build() {
return {
_sidebar_portal: SidebarResolverService,
};
}
}