diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index adaa0ac..532be8f 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -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; diff --git a/frontend/src/app/components/footer/footer.module.ts b/frontend/src/app/components/footer/footer.module.ts index caf46e2..dec2cb3 100644 --- a/frontend/src/app/components/footer/footer.module.ts +++ b/frontend/src/app/components/footer/footer.module.ts @@ -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 {} diff --git a/frontend/src/app/components/header/header.module.ts b/frontend/src/app/components/header/header.module.ts index 70896f6..b63bf5e 100644 --- a/frontend/src/app/components/header/header.module.ts +++ b/frontend/src/app/components/header/header.module.ts @@ -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, diff --git a/frontend/src/app/guards/guards.module.ts b/frontend/src/app/guards/guards.module.ts index 1379427..3c5b3b2 100644 --- a/frontend/src/app/guards/guards.module.ts +++ b/frontend/src/app/guards/guards.module.ts @@ -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: [], }) diff --git a/frontend/src/app/guards/permission.guard.ts b/frontend/src/app/guards/permission.guard.ts index 872ea10..bc46c3e 100644 --- a/frontend/src/app/guards/permission.guard.ts +++ b/frontend/src/app/guards/permission.guard.ts @@ -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; + } } diff --git a/frontend/src/app/models/picsur-routes.ts b/frontend/src/app/models/picsur-routes.ts index 264d48a..e9f514e 100644 --- a/frontend/src/app/models/picsur-routes.ts +++ b/frontend/src/app/models/picsur-routes.ts @@ -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; + _sidebar_portal?: Portal; }; export type PRoute = Route & { diff --git a/frontend/src/app/routes/settings/settings-general/settings-general.component.ts b/frontend/src/app/routes/settings/settings-general/settings-general.component.ts index 3d69789..c216dd8 100644 --- a/frontend/src/app/routes/settings/settings-general/settings-general.component.ts +++ b/frontend/src/app/routes/settings/settings-general/settings-general.component.ts @@ -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); + }); + } } diff --git a/frontend/src/app/routes/settings/settings-general/settings-home.module.ts b/frontend/src/app/routes/settings/settings-general/settings-general.module.ts similarity index 80% rename from frontend/src/app/routes/settings/settings-general/settings-home.module.ts rename to frontend/src/app/routes/settings/settings-general/settings-general.module.ts index 9dc34a8..9ecaf75 100644 --- a/frontend/src/app/routes/settings/settings-general/settings-home.module.ts +++ b/frontend/src/app/routes/settings/settings-general/settings-general.module.ts @@ -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], diff --git a/frontend/src/app/routes/settings/settings-general/settings-home.routing.module.ts b/frontend/src/app/routes/settings/settings-general/settings-general.routing.module.ts similarity index 100% rename from frontend/src/app/routes/settings/settings-general/settings-home.routing.module.ts rename to frontend/src/app/routes/settings/settings-general/settings-general.routing.module.ts diff --git a/frontend/src/app/routes/settings/settings-sidebar/settings-sidebar.component.html b/frontend/src/app/routes/settings/settings-sidebar/settings-sidebar.component.html index 8c27d21..1d1899d 100644 --- a/frontend/src/app/routes/settings/settings-sidebar/settings-sidebar.component.html +++ b/frontend/src/app/routes/settings/settings-sidebar/settings-sidebar.component.html @@ -1,5 +1,4 @@ - Big Peepee Personal { + 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; } } diff --git a/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.html b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.html new file mode 100644 index 0000000..86c5a80 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.html @@ -0,0 +1,2 @@ +

Settings Syspref

+ diff --git a/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.ts b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.ts new file mode 100644 index 0000000..2c18349 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.component.ts @@ -0,0 +1,10 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + templateUrl: './settings-syspref.component.html', +}) +export class SettingsSysprefComponent implements OnInit { + constructor() {} + + ngOnInit(): void {} +} diff --git a/frontend/src/app/routes/settings/settings-syspref/settings-syspref.module.ts b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.module.ts new file mode 100644 index 0000000..d58e916 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.module.ts @@ -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 {} diff --git a/frontend/src/app/routes/settings/settings-syspref/settings-syspref.routing.module.ts b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.routing.module.ts new file mode 100644 index 0000000..69ed49f --- /dev/null +++ b/frontend/src/app/routes/settings/settings-syspref/settings-syspref.routing.module.ts @@ -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 {} diff --git a/frontend/src/app/routes/settings/settings.module.ts b/frontend/src/app/routes/settings/settings.module.ts index 8f0df50..d9c44f3 100644 --- a/frontend/src/app/routes/settings/settings.module.ts +++ b/frontend/src/app/routes/settings/settings.module.ts @@ -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) {} +} diff --git a/frontend/src/app/routes/settings/settings.routing.module.ts b/frontend/src/app/routes/settings/settings.routing.module.ts index ec5f816..431ffb6 100644 --- a/frontend/src/app/routes/settings/settings.routing.module.ts +++ b/frontend/src/app/routes/settings/settings.routing.module.ts @@ -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, }, ], }; diff --git a/frontend/src/app/services/api/api.module.ts b/frontend/src/app/services/api/api.module.ts deleted file mode 100644 index 29f0579..0000000 --- a/frontend/src/app/services/api/api.module.ts +++ /dev/null @@ -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 {} diff --git a/frontend/src/app/services/api/permission.service.ts b/frontend/src/app/services/api/permission.service.ts index db9fc4d..d5eea30 100644 --- a/frontend/src/app/services/api/permission.service.ts +++ b/frontend/src/app/services/api/permission.service.ts @@ -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); }); } diff --git a/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts b/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts new file mode 100644 index 0000000..ade80d7 --- /dev/null +++ b/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts @@ -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 | 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, + }; + } +}