diff --git a/src/app/_guards/admin.guard.ts b/src/app/_guards/admin.guard.ts index 2297e381c..d59b8643c 100644 --- a/src/app/_guards/admin.guard.ts +++ b/src/app/_guards/admin.guard.ts @@ -16,7 +16,7 @@ export class AdminGuard implements CanActivate { // this automaticallys subs due to being router guard return this.accountService.currentUser$.pipe( map((user: User) => { - if (user && user.roles.includes('Admin')) { + if (this.accountService.hasAdminRole(user)) { return true; } diff --git a/src/app/_models/library.ts b/src/app/_models/library.ts index 19fa61c19..a56f2c122 100644 --- a/src/app/_models/library.ts +++ b/src/app/_models/library.ts @@ -1,4 +1,5 @@ export interface Library { + id: number; name: string; coverImage: string; type: any; diff --git a/src/app/_models/series.ts b/src/app/_models/series.ts new file mode 100644 index 000000000..78085a077 --- /dev/null +++ b/src/app/_models/series.ts @@ -0,0 +1,11 @@ +import { Volume } from './volume'; + +export interface Series { + id: number; + name: string; + originalName: string; + sortName: string; + summary: string; + coverImage: string; + volumes: Volume[]; +} diff --git a/src/app/_models/volume.ts b/src/app/_models/volume.ts new file mode 100644 index 000000000..77a7629fa --- /dev/null +++ b/src/app/_models/volume.ts @@ -0,0 +1,6 @@ +export interface Volume { + id: number; + number: string; + files: Array; + coverImage: string; +} diff --git a/src/app/_services/account.service.ts b/src/app/_services/account.service.ts index dc6449c6e..9da467d08 100644 --- a/src/app/_services/account.service.ts +++ b/src/app/_services/account.service.ts @@ -21,6 +21,10 @@ export class AccountService { constructor(private httpClient: HttpClient) { } + hasAdminRole(user: User) { + return user && user.roles.includes('Admin'); + } + login(model: any): Observable { return this.httpClient.post(this.baseUrl + 'account/login', model).pipe( map((response: User) => { diff --git a/src/app/_services/library.service.ts b/src/app/_services/library.service.ts index 49399a650..5243bf517 100644 --- a/src/app/_services/library.service.ts +++ b/src/app/_services/library.service.ts @@ -26,12 +26,13 @@ export class LibraryService { } getLibrariesForMember(username: string) { - return this.httpClient.get(this.baseUrl + 'library/' + username); + return this.httpClient.get(this.baseUrl + 'library/libraries-for?username=' + username); } updateLibrariesForMember(username: string, selectedLibraries: Library[]) { - return this.httpClient.post(this.baseUrl + '/library/update-for', {username, selectedLibraries}); + return this.httpClient.post(this.baseUrl + 'library/update-for', {username, selectedLibraries}); } + } diff --git a/src/app/_services/member.service.ts b/src/app/_services/member.service.ts index bb1888084..4f1ca9fd4 100644 --- a/src/app/_services/member.service.ts +++ b/src/app/_services/member.service.ts @@ -19,7 +19,7 @@ export class MemberService { adminExists() { return this.httpClient.get(this.baseUrl + 'admin/exists'); } - + deleteMember(username: string) { return this.httpClient.delete(this.baseUrl + 'users/delete-user?username=' + username); } diff --git a/src/app/_services/series.service.ts b/src/app/_services/series.service.ts new file mode 100644 index 000000000..c1fc3f188 --- /dev/null +++ b/src/app/_services/series.service.ts @@ -0,0 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { Series } from '../_models/series'; +import { Volume } from '../_models/volume'; + +@Injectable({ + providedIn: 'root' +}) +export class SeriesService { + + baseUrl = environment.apiUrl; + + constructor(private httpClient: HttpClient) { } + + getSeriesForLibrary(libraryId: number) { + return this.httpClient.get(this.baseUrl + 'library/series?libraryId=' + libraryId); + } + + getSeries(seriesId: number) { + return this.httpClient.get(this.baseUrl + 'series/' + seriesId); + } + + getVolumes(seriesId: number) { + return this.httpClient.get(this.baseUrl + 'series/volumes?seriesId=' + seriesId); + } +} diff --git a/src/app/admin/manage-users/manage-users.component.html b/src/app/admin/manage-users/manage-users.component.html index 678bac385..f247ba2a2 100644 --- a/src/app/admin/manage-users/manage-users.component.html +++ b/src/app/admin/manage-users/manage-users.component.html @@ -13,7 +13,6 @@ {{member.username | titlecase}} Admin
-
diff --git a/src/app/admin/manage-users/manage-users.component.ts b/src/app/admin/manage-users/manage-users.component.ts index 07008a465..4cfa0eb19 100644 --- a/src/app/admin/manage-users/manage-users.component.ts +++ b/src/app/admin/manage-users/manage-users.component.ts @@ -61,11 +61,8 @@ export class ManageUsersComponent implements OnInit { }); } - openChangeRole(member: Member) { - - } - deleteUser(member: Member) { + // TODO: Use a modal for this confirm if (confirm('Are you sure you want to delete this user?')) { this.memberService.deleteMember(member.username).subscribe(() => { this.loadMembers(); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index f10a682e3..9b1705767 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,8 +1,9 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home/home.component'; +import { LibraryDetailComponent } from './library-detail/library-detail.component'; import { LibraryComponent } from './library/library.component'; -import { AdminGuard } from './_guards/admin.guard'; +import { SeriesDetailComponent } from './series-detail/series-detail.component'; const routes: Routes = [ {path: '', component: HomeComponent}, @@ -11,7 +12,8 @@ const routes: Routes = [ loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }, {path: 'library', component: LibraryComponent}, - {path: 'home', component: HomeComponent}, + {path: 'library/:id', component: LibraryDetailComponent}, // NOTE: Should I put a guard up to prevent unauthorized access to libraries and series? + {path: 'series/:id', component: SeriesDetailComponent}, {path: '**', component: HomeComponent, pathMatch: 'full'} ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 14b772fe4..41ce34cad 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -15,6 +15,8 @@ import { ToastrModule } from 'ngx-toastr'; import { ErrorInterceptor } from './_interceptors/error.interceptor'; import { LibraryComponent } from './library/library.component'; import { SharedModule } from './shared/shared.module'; +import { LibraryDetailComponent } from './library-detail/library-detail.component'; +import { SeriesDetailComponent } from './series-detail/series-detail.component'; @@ -25,6 +27,8 @@ import { SharedModule } from './shared/shared.module'; NavHeaderComponent, UserLoginComponent, LibraryComponent, + LibraryDetailComponent, + SeriesDetailComponent, ], imports: [ HttpClientModule, diff --git a/src/app/library-detail/library-detail.component.html b/src/app/library-detail/library-detail.component.html new file mode 100644 index 000000000..15bd6e363 --- /dev/null +++ b/src/app/library-detail/library-detail.component.html @@ -0,0 +1,12 @@ +
+

Title (Manga/Recently Added)

+
+
+ +
+
+ + + Nothing here.... + +
\ No newline at end of file diff --git a/src/app/library-detail/library-detail.component.scss b/src/app/library-detail/library-detail.component.scss new file mode 100644 index 000000000..7f7daffd5 --- /dev/null +++ b/src/app/library-detail/library-detail.component.scss @@ -0,0 +1,4 @@ +.card { + height: 400; + width: 200; +} \ No newline at end of file diff --git a/src/app/library-detail/library-detail.component.ts b/src/app/library-detail/library-detail.component.ts new file mode 100644 index 000000000..686726624 --- /dev/null +++ b/src/app/library-detail/library-detail.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Library } from '../_models/library'; +import { Series } from '../_models/series'; +import { SeriesService } from '../_services/series.service'; + +@Component({ + selector: 'app-library-detail', + templateUrl: './library-detail.component.html', + styleUrls: ['./library-detail.component.scss'] +}) +export class LibraryDetailComponent implements OnInit { + + libraryId!: number; + series: Series[] = []; + + + constructor(private route: ActivatedRoute, private router: Router, private seriesService: SeriesService) { + const routeId = this.route.snapshot.paramMap.get('id'); + if (routeId === null) { + console.error('No library id was passed. Redirecting to home'); + this.router.navigateByUrl('/home'); + return; + } + this.libraryId = parseInt(routeId, 10); + this.seriesService.getSeriesForLibrary(this.libraryId).subscribe(series => { + this.series = series; + }); + } + + ngOnInit(): void { + } + + seriesClicked(series: Series) { + this.router.navigateByUrl('/series/' + series.id); + } + +} diff --git a/src/app/library/library.component.html b/src/app/library/library.component.html index cedd8c65f..666f33e34 100644 --- a/src/app/library/library.component.html +++ b/src/app/library/library.component.html @@ -1 +1,7 @@ -

library works!

+ +

Libraries

+
+
+ +
+
\ No newline at end of file diff --git a/src/app/library/library.component.ts b/src/app/library/library.component.ts index b33f10b9c..c4f027c73 100644 --- a/src/app/library/library.component.ts +++ b/src/app/library/library.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { take } from 'rxjs/operators'; +import { CardItemAction } from '../shared/card-item/card-item.component'; import { Library } from '../_models/library'; import { User } from '../_models/user'; import { AccountService } from '../_services/account.service'; @@ -15,16 +17,29 @@ export class LibraryComponent implements OnInit { user: User | undefined; libraries: Library[] = []; + actions: CardItemAction[] = []; - constructor(public accountService: AccountService, private memberService: MemberService, private libraryService: LibraryService) { } + constructor(public accountService: AccountService, private libraryService: LibraryService, private router: Router) { } ngOnInit(): void { this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.user = user; - // this.libraryService.getLibrariesForUser(this.user.username).subscribe(libraries => { - // this.libraries = libraries; - // }); + if (this.accountService.hasAdminRole(user)) { + this.actions = [ + {title: 'Scan Library', callback: (data: Library) => { + console.log('You tried to scan library: ' + data.name); + }} + ]; + } + this.libraryService.getLibrariesForMember(this.user.username).subscribe(libraries => { + this.libraries = libraries; + console.log('Libraries: ', this.libraries); + }); }); } + handleNavigation(event: any, library: Library) { + this.router.navigateByUrl('/library/' + library.id); + } + } diff --git a/src/app/series-detail/series-detail.component.html b/src/app/series-detail/series-detail.component.html new file mode 100644 index 000000000..7154f9845 --- /dev/null +++ b/src/app/series-detail/series-detail.component.html @@ -0,0 +1,29 @@ +
+
+
+ + +
+
+

{{series.name | titlecase}}

+
+ +
+
+ +
+
+

{{series?.summary}}

+
+ +
+
+ +

Volumes

+
+
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/series-detail/series-detail.component.scss b/src/app/series-detail/series-detail.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/series-detail/series-detail.component.ts b/src/app/series-detail/series-detail.component.ts new file mode 100644 index 000000000..f0e8a0455 --- /dev/null +++ b/src/app/series-detail/series-detail.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Series } from '../_models/series'; +import { Volume } from '../_models/volume'; +import { SeriesService } from '../_services/series.service'; + +@Component({ + selector: 'app-series-detail', + templateUrl: './series-detail.component.html', + styleUrls: ['./series-detail.component.scss'] +}) +export class SeriesDetailComponent implements OnInit { + + series: Series | undefined; + volumes: Volume[] = []; + + constructor(private route: ActivatedRoute, private seriesService: SeriesService) { } + + ngOnInit(): void { + + const routeId = this.route.snapshot.paramMap.get('id'); + if (routeId === null) { + console.error('No library id was passed. Redirecting to home'); + //this.router.navigateByUrl('/home'); + return; + } + const seriesId = parseInt(routeId, 10); + this.seriesService.getSeries(seriesId).subscribe(series => { + this.series = series; + this.seriesService.getVolumes(this.series.id).subscribe(volumes => { + this.volumes = volumes; + }); + }); + } + + openVolume(volume: Volume) { + alert('TODO: Let user read Manga'); + } + +} diff --git a/src/app/shared/card-item/card-item.component.html b/src/app/shared/card-item/card-item.component.html new file mode 100644 index 000000000..976a075f8 --- /dev/null +++ b/src/app/shared/card-item/card-item.component.html @@ -0,0 +1,17 @@ +
+ {{title}} + +
+
{{title}}
+ +
+
+ +
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/shared/card-item/card-item.component.scss b/src/app/shared/card-item/card-item.component.scss new file mode 100644 index 000000000..9987ed9cc --- /dev/null +++ b/src/app/shared/card-item/card-item.component.scss @@ -0,0 +1,13 @@ +.card { + margin: 5px; + max-width: 130px; + max-height: 195px; +} + +.card-body { + padding: 5px !important; +} + +.dropdown-toggle:after { + content: none !important; +} \ No newline at end of file diff --git a/src/app/shared/card-item/card-item.component.ts b/src/app/shared/card-item/card-item.component.ts new file mode 100644 index 000000000..ff41443ee --- /dev/null +++ b/src/app/shared/card-item/card-item.component.ts @@ -0,0 +1,44 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + + +export interface CardItemAction { + title: string; + callback: (data: any) => void; +} + +@Component({ + selector: 'app-card-item', + templateUrl: './card-item.component.html', + styleUrls: ['./card-item.component.scss'] +}) +export class CardItemComponent implements OnInit { + + @Input() imageUrl = ''; + @Input() title = ''; + @Input() actions: CardItemAction[] = []; // TODO: Create a factory that generates actions based on if admin, etc. for each card type. + @Input() entity: any; // This is the entity we are representing. It will be returned if an action is executed. + @Output() clicked = new EventEmitter(); + + placeholderImage = 'assets/images/image-placeholder.jpg'; + + constructor() { } + + ngOnInit(): void { + } + + handleClick() { + this.clicked.emit(this.title); + } + + isNullOrEmpty(val: string) { + return val === null || val === undefined || val === ''; + } + + performAction(event: any, action: CardItemAction) { + event.stopPropagation(); + if (typeof action.callback === 'function') { + action.callback(this.entity); + } + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index d3b94d520..01a2b702f 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -2,17 +2,21 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RegisterMemberComponent } from './register-member/register-member.component'; import { ReactiveFormsModule } from '@angular/forms'; +import { CardItemComponent } from './card-item/card-item.component'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ - declarations: [RegisterMemberComponent], + declarations: [RegisterMemberComponent, CardItemComponent], imports: [ CommonModule, - ReactiveFormsModule + ReactiveFormsModule, + NgbDropdownModule ], exports: [ - RegisterMemberComponent + RegisterMemberComponent, + CardItemComponent ] }) export class SharedModule { } diff --git a/src/assets/images/image-placeholder.jpg b/src/assets/images/image-placeholder.jpg new file mode 100644 index 000000000..872022b56 Binary files /dev/null and b/src/assets/images/image-placeholder.jpg differ