mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Implementing the browse all page with custom sorts
This commit is contained in:
parent
281f0e2024
commit
7e77e804b6
@ -6,17 +6,23 @@ import {NotFoundComponent} from './not-found/not-found.component';
|
|||||||
import {PlayerComponent} from "./pages/player/player.component";
|
import {PlayerComponent} from "./pages/player/player.component";
|
||||||
import {SearchComponent} from "./pages/search/search.component";
|
import {SearchComponent} from "./pages/search/search.component";
|
||||||
import {CollectionResolverService} from "./services/resolvers/collection-resolver.service";
|
import {CollectionResolverService} from "./services/resolvers/collection-resolver.service";
|
||||||
import {LibraryResolverService} from './services/resolvers/library-resolver.service';
|
import {PageResolver} from './services/resolvers/library-resolver.service';
|
||||||
import {PeopleResolverService} from "./services/resolvers/people-resolver.service";
|
import {PeopleResolverService} from "./services/resolvers/people-resolver.service";
|
||||||
import {SearchResolverService} from "./services/resolvers/search-resolver.service";
|
import {SearchResolverService} from "./services/resolvers/search-resolver.service";
|
||||||
import {ShowResolverService} from './services/resolvers/show-resolver.service';
|
import {ShowResolverService} from './services/resolvers/show-resolver.service';
|
||||||
import {StreamResolverService} from "./services/resolvers/stream-resolver.service";
|
import {StreamResolverService} from "./services/resolvers/stream-resolver.service";
|
||||||
import {ShowDetailsComponent} from './pages/show-details/show-details.component';
|
import {ShowDetailsComponent} from './pages/show-details/show-details.component';
|
||||||
import {AuthGuard} from "./auth/misc/authenticated-guard.service";
|
import {AuthGuard} from "./auth/misc/authenticated-guard.service";
|
||||||
|
import {LibraryItem} from "../models/library-item";
|
||||||
|
import {CrudApi, LibraryItemService, LibraryService} from "./services/api.service";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "browse", component: LibraryItemGridComponent, pathMatch: "full", resolve: { shows: LibraryResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
{path: "browse", component: LibraryItemGridComponent, pathMatch: "full",
|
||||||
{path: "browse/:library-slug", component: LibraryItemGridComponent, resolve: { shows: LibraryResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
resolve: { items: PageResolver.forResource<LibraryItem>("items") },
|
||||||
|
canLoad: [AuthGuard.forPermissions("read")],
|
||||||
|
canActivate: [AuthGuard.forPermissions("read")]
|
||||||
|
},
|
||||||
|
{path: "browse/:library-slug", component: LibraryItemGridComponent, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
||||||
{path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
{path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
||||||
{path: "collection/:collection-slug", component: CollectionComponent, resolve: { collection: CollectionResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
{path: "collection/:collection-slug", component: CollectionComponent, resolve: { collection: CollectionResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
||||||
{path: "people/:people-slug", component: CollectionComponent, resolve: { collection: PeopleResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
{path: "people/:people-slug", component: CollectionComponent, resolve: { collection: PeopleResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
||||||
@ -32,7 +38,9 @@ const routes: Routes = [
|
|||||||
})],
|
})],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: [
|
providers: [
|
||||||
LibraryResolverService,
|
LibraryService,
|
||||||
|
LibraryItemService,
|
||||||
|
PageResolver.resolvers,
|
||||||
ShowResolverService,
|
ShowResolverService,
|
||||||
CollectionResolverService,
|
CollectionResolverService,
|
||||||
PeopleResolverService,
|
PeopleResolverService,
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import {Event, Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError} from '@angular/router';
|
||||||
import { Event, Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
|
import {Location} from "@angular/common";
|
||||||
import * as $ from "jquery";
|
|
||||||
import { Location } from "@angular/common";
|
|
||||||
import {MatDialog} from "@angular/material/dialog";
|
import {MatDialog} from "@angular/material/dialog";
|
||||||
import {Account} from "../models/account";
|
import {Account} from "../models/account";
|
||||||
import {AccountComponent} from "./auth/account/account.component";
|
import {AccountComponent} from "./auth/account/account.component";
|
||||||
import {AuthService} from "./auth/auth.service";
|
import {AuthService} from "./auth/auth.service";
|
||||||
import {Library} from "../models/library";
|
import {Library} from "../models/library";
|
||||||
import {Page} from "../models/page";
|
import {LibraryService} from "./services/api.service";
|
||||||
|
import * as $ from "jquery";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -20,13 +19,13 @@ export class AppComponent
|
|||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
isLoading: boolean = false;
|
isLoading: boolean = false;
|
||||||
|
|
||||||
constructor(private http: HttpClient,
|
constructor(private libraryService: LibraryService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
public authManager: AuthService,
|
public authManager: AuthService,
|
||||||
public dialog: MatDialog)
|
public dialog: MatDialog)
|
||||||
{
|
{
|
||||||
http.get<Page<Library>>("api/libraries").subscribe(result =>
|
libraryService.getAll().subscribe(result =>
|
||||||
{
|
{
|
||||||
this.libraries = result.items;
|
this.libraries = result.items;
|
||||||
}, error => console.error(error));
|
}, error => console.error(error));
|
||||||
|
@ -3,28 +3,32 @@
|
|||||||
<mat-icon>filter_list</mat-icon>
|
<mat-icon>filter_list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-button matTooltipPosition="below" matTooltip="Sort" [matMenuTriggerFor]="sortMenu">
|
<button mat-button matTooltipPosition="below" matTooltip="Sort" [matMenuTriggerFor]="sortMenu">
|
||||||
<mat-icon>sort</mat-icon> Sort by {{this.sortType}} <i *ngIf="this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="!this.sortUp" class="material-icons arrow">arrow_downward</i>
|
<mat-icon>sort</mat-icon> Sort by {{this.sortType}}
|
||||||
|
<i *ngIf="this.sortUp" class="material-icons arrow">arrow_upward</i>
|
||||||
|
<i *ngIf="!this.sortUp" class="material-icons arrow">arrow_downward</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-menu #sortMenu="matMenu">
|
<mat-menu #sortMenu="matMenu">
|
||||||
<div *ngFor="let type of this.sortTypes">
|
<div *ngFor="let type of this.sortKeys">
|
||||||
<button *ngIf="type != this.sortType; else elseBlock;" mat-menu-item (click)="sort(type, true)">
|
<button *ngIf="type != this.sortType; else elseBlock;" mat-menu-item (click)="sort(type, true)">
|
||||||
Sort by {{type}}
|
Sort by {{type}}
|
||||||
</button>
|
</button>
|
||||||
<ng-template #elseBlock>
|
<ng-template #elseBlock>
|
||||||
<button mat-menu-item (click)="sort(type, !this.sortUp)">
|
<button mat-menu-item (click)="sort(type, !this.sortUp)">
|
||||||
Sort by {{type}} <i *ngIf="!this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="this.sortUp" class="material-icons arrow">arrow_downward</i>
|
Sort by {{type}}
|
||||||
|
<i *ngIf="!this.sortUp" class="material-icons arrow">arrow_upward</i>
|
||||||
|
<i *ngIf="this.sortUp" class="material-icons arrow">arrow_downward</i>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<div class="container-fluid justify-content-center">
|
<div class="container-fluid justify-content-center">
|
||||||
<a class="show" *ngFor="let show of this.shows" [href]="getLink(show)" [routerLink]="getLink(show)">
|
<a class="show" *ngFor="let item of this.page.items" [href]="getLink(item)" [routerLink]="getLink(item)">
|
||||||
<div matRipple [style.background-image]="getThumb(show.slug)" > </div>
|
<div matRipple [style.background-image]="getThumb(item.slug)" > </div>
|
||||||
<p class="title">{{show.title}}</p>
|
<p class="title">{{item.title}}</p>
|
||||||
<p class="date" *ngIf="show.endYear && show.startYear != show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
|
<p class="date" *ngIf="item.endYear && item.startYear != item.endYear; else elseBlock">{{item.startYear}} - {{item.endYear}}</p>
|
||||||
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
|
<ng-template #elseBlock><p class="date">{{item.startYear}}</p></ng-template>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
import {Component, Input} from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import {DomSanitizer} from '@angular/platform-browser';
|
||||||
import { Show } from "../../../models/show";
|
import {ItemType, LibraryItem} from "../../../models/library-item";
|
||||||
|
import {Page} from "../../../models/page";
|
||||||
|
import {LibraryItemService} from "../../services/api.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-browse',
|
selector: 'app-browse',
|
||||||
@ -10,18 +12,17 @@ import { Show } from "../../../models/show";
|
|||||||
})
|
})
|
||||||
export class LibraryItemGridComponent
|
export class LibraryItemGridComponent
|
||||||
{
|
{
|
||||||
@Input() shows: Show[];
|
@Input() page: Page<LibraryItem>;
|
||||||
@Input() sortEnabled: boolean = true;
|
@Input() sortEnabled: boolean = true;
|
||||||
sortType: string = "title";
|
sortType: string = "title";
|
||||||
|
sortKeys: string[] = ["title", "start year", "end year", "status", "type"]
|
||||||
sortUp: boolean = true;
|
sortUp: boolean = true;
|
||||||
|
|
||||||
sortTypes: string[] = ["title", "release date"];
|
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private items: LibraryItemService)
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer)
|
|
||||||
{
|
{
|
||||||
this.route.data.subscribe((data) =>
|
this.route.data.subscribe((data) =>
|
||||||
{
|
{
|
||||||
this.shows = data.shows;
|
this.page = data.items;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,9 +31,9 @@ export class LibraryItemGridComponent
|
|||||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
getLink(show: Show)
|
getLink(show: LibraryItem)
|
||||||
{
|
{
|
||||||
if (show.isCollection)
|
if (show.type == ItemType.Collection)
|
||||||
return "/collection/" + show.slug;
|
return "/collection/" + show.slug;
|
||||||
else
|
else
|
||||||
return "/show/" + show.slug;
|
return "/show/" + show.slug;
|
||||||
@ -43,19 +44,7 @@ export class LibraryItemGridComponent
|
|||||||
this.sortType = type;
|
this.sortType = type;
|
||||||
this.sortUp = order;
|
this.sortUp = order;
|
||||||
|
|
||||||
if (type == this.sortTypes[0])
|
this.items.getAll({sort: `${this.sortType.replace(/\s/g, "")}:${this.sortUp ? "asc" : "desc"}`})
|
||||||
{
|
.subscribe(x => this.page = x);
|
||||||
if (order)
|
|
||||||
this.shows.sort((a, b) => { if (a.title < b.title) return -1; else if (a.title > b.title) return 1; return 0; });
|
|
||||||
else
|
|
||||||
this.shows.sort((a, b) => { if (a.title < b.title) return 1; else if (a.title > b.title) return -1; return 0; });
|
|
||||||
}
|
|
||||||
else if (type == this.sortTypes[1])
|
|
||||||
{
|
|
||||||
if (order)
|
|
||||||
this.shows.sort((a, b) => a.startYear - b.startYear);
|
|
||||||
else
|
|
||||||
this.shows.sort((a, b) => b.startYear - a.startYear);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
src/app/services/api.service.ts
Normal file
77
src/app/services/api.service.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {Observable} from "rxjs"
|
||||||
|
import {map} from "rxjs/operators"
|
||||||
|
import {Page} from "../../models/page";
|
||||||
|
import {IResource} from "../../models/resources/resource";
|
||||||
|
import {Library} from "../../models/library";
|
||||||
|
import {LibraryItem} from "../../models/library-item";
|
||||||
|
|
||||||
|
class CrudApi<T extends IResource>
|
||||||
|
{
|
||||||
|
constructor(private client: HttpClient, private route: string) {}
|
||||||
|
|
||||||
|
get(id: number | string): Observable<T>
|
||||||
|
{
|
||||||
|
return this.client.get<T>(`/api/${this.route}/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(args: {sort: string} = null): Observable<Page<T>>
|
||||||
|
{
|
||||||
|
let params: string = "?";
|
||||||
|
if (args && args.sort)
|
||||||
|
params += "sortBy=" + args.sort;
|
||||||
|
if (params == "?")
|
||||||
|
params = "";
|
||||||
|
return this.client.get<Page<T>>(`/api/${this.route}${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNext(page: Page<T>): Observable<Page<T>>
|
||||||
|
{
|
||||||
|
if (page.next == null)
|
||||||
|
return;
|
||||||
|
return this.client.get<Page<T>>(page.next).pipe(map(x =>
|
||||||
|
{
|
||||||
|
x.items = page.items.concat(x.items);
|
||||||
|
x.count += page.count;
|
||||||
|
return x;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
create(item: T): Observable<T>
|
||||||
|
{
|
||||||
|
return this.client.post<T>(`/api/${this.route}`, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(item: T): Observable<T>
|
||||||
|
{
|
||||||
|
return this.client.put<T>(`/api/${this.route}`, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(item: T): Observable<T>
|
||||||
|
{
|
||||||
|
return this.client.delete<T>(`/api/${this.route}/${item.slug}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LibraryService extends CrudApi<Library>
|
||||||
|
{
|
||||||
|
constructor(client: HttpClient)
|
||||||
|
{
|
||||||
|
super(client, "libraries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LibraryItemService extends CrudApi<LibraryItem>
|
||||||
|
{
|
||||||
|
constructor(client: HttpClient)
|
||||||
|
{
|
||||||
|
super(client, "items");
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
import {Injectable} from "@angular/core";
|
|
||||||
import {HttpClient} from "@angular/common/http";
|
|
||||||
import {Observable} from "rxjs"
|
|
||||||
import {Page} from "../../models/page";
|
|
||||||
import {IResource} from "../../models/resources/resource";
|
|
||||||
|
|
||||||
class CrudApi<T extends IResource>
|
|
||||||
{
|
|
||||||
constructor(private client: HttpClient, private route: string) {}
|
|
||||||
|
|
||||||
get(id: number | string): Observable<T>
|
|
||||||
{
|
|
||||||
return this.client.get<T>(`/api/${this.route}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAll(id: number | string): Observable<Page<T>>
|
|
||||||
{
|
|
||||||
return this.client.get<Page<T>>(`/api/${this.route}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
create(item: T): Observable<T>
|
|
||||||
{
|
|
||||||
return this.client.post<T>(`/api/${this.route}`, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
edit(item: T): Observable<T>
|
|
||||||
{
|
|
||||||
return this.client.put<T>(`/api/${this.route}`, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(item: T): Observable<T>
|
|
||||||
{
|
|
||||||
return this.client.delete<T>(`/api/${this.route}/${item.slug}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class LibraryService
|
|
||||||
{
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
get()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,42 +4,36 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||||
import { EMPTY, Observable } from 'rxjs';
|
import { EMPTY, Observable } from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import { Show } from "../../../models/show";
|
import {Page} from "../../../models/page";
|
||||||
|
import {IResource} from "../../../models/resources/resource";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LibraryResolverService implements Resolve<Show[]>
|
export class PageResolver
|
||||||
{
|
{
|
||||||
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
public static resolvers: any[] = [];
|
||||||
|
|
||||||
resolve(route: ActivatedRouteSnapshot): Show[] | Observable<Show[]> | Promise<Show[]>
|
static forResource<T extends IResource>(resource: string)
|
||||||
{
|
{
|
||||||
let slug: string = route.paramMap.get("library-slug");
|
@Injectable()
|
||||||
|
class Resolver<T> implements Resolve<Page<T>>
|
||||||
|
{
|
||||||
|
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
||||||
|
|
||||||
if (slug == null)
|
resolve(route: ActivatedRouteSnapshot): Page<T> | Observable<Page<T>> | Promise<Page<T>>
|
||||||
{
|
|
||||||
return this.http.get<Show[]>("api/shows").pipe(catchError((error: HttpErrorResponse) =>
|
|
||||||
{
|
{
|
||||||
console.log(error.status + " - " + error.message);
|
return this.http.get<Page<T>>(`api/${resource}`).pipe(catchError((error: HttpErrorResponse) =>
|
||||||
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
|
||||||
return EMPTY;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.http.get<Show[]>("api/libraries/" + slug).pipe(catchError((error: HttpErrorResponse) =>
|
|
||||||
{
|
|
||||||
console.log(error.status + " - " + error.message);
|
|
||||||
if (error.status == 404)
|
|
||||||
{
|
{
|
||||||
this.snackBar.open("Library \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
console.log(error.status + " - " + error.message);
|
||||||
}
|
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
|
||||||
else
|
horizontalPosition: "left",
|
||||||
{
|
panelClass: ['snackError'],
|
||||||
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
duration: 2500 }
|
||||||
}
|
);
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
PageResolver.resolvers.push(Resolver);
|
||||||
|
return Resolver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
enum ItemType
|
import {IResource} from "./resources/resource";
|
||||||
|
|
||||||
|
export enum ItemType
|
||||||
{
|
{
|
||||||
Show,
|
Show,
|
||||||
Movie,
|
Movie,
|
||||||
Collection
|
Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LibraryItem
|
export interface LibraryItem extends IResource
|
||||||
{
|
{
|
||||||
ID: number
|
title: string
|
||||||
Slug: string
|
overview: string
|
||||||
Title: string
|
status: string
|
||||||
Overview: string
|
trailerUrl: string
|
||||||
Status: string
|
startYear: number
|
||||||
TrailerUrl: string
|
endYear: number
|
||||||
StartYear: number
|
poster: string
|
||||||
EndYear: number
|
type: ItemType
|
||||||
Poster: string
|
|
||||||
Type: ItemType
|
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
export interface Library
|
import {IResource} from "./resources/resource";
|
||||||
|
|
||||||
|
export interface Library extends IResource
|
||||||
{
|
{
|
||||||
id: number;
|
id: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user