mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-22 15:00:35 -04:00
Updating the search page
This commit is contained in:
parent
cee5b62469
commit
cf2196b153
@ -1,7 +1,7 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ItemsGridComponent} from './components/items-grid/items-grid.component';
|
||||
import {NotFoundComponent} from './not-found/not-found.component';
|
||||
import {NotFoundComponent} from './pages/not-found/not-found.component';
|
||||
import {PageResolver} from './services/resolvers/page-resolver.service';
|
||||
import {ShowDetailsComponent} from './pages/show-details/show-details.component';
|
||||
import {AuthGuard} from "./auth/misc/authenticated-guard.service";
|
||||
@ -18,7 +18,8 @@ import {Show} from "../models/show";
|
||||
import {ItemResolver} from "./services/resolvers/item-resolver.service";
|
||||
import {CollectionComponent} from "./pages/collection/collection.component";
|
||||
import {Collection} from "../models/collection";
|
||||
import {People} from "../models/people";
|
||||
import {SearchComponent} from "./pages/search/search.component";
|
||||
import {SearchResult} from "../models/search-result";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "browse", component: ItemsGridComponent, pathMatch: "full",
|
||||
@ -55,8 +56,14 @@ const routes: Routes = [
|
||||
canLoad: [AuthGuard.forPermissions("read")],
|
||||
canActivate: [AuthGuard.forPermissions("read")]
|
||||
},
|
||||
|
||||
{path: "search/:query", component: SearchComponent,
|
||||
resolve: { items: ItemResolver.forResource<SearchResult>("search/:query") },
|
||||
canLoad: [AuthGuard.forPermissions("read")],
|
||||
canActivate: [AuthGuard.forPermissions("read")]
|
||||
},
|
||||
|
||||
// {path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService }, canLoad: [AuthGuard.forPermissions("play")], canActivate: [AuthGuard.forPermissions("play")]},
|
||||
// {path: "search/:query", component: SearchComponent, resolve: { items: SearchResolverService }, canLoad: [AuthGuard.forPermissions("read")], canActivate: [AuthGuard.forPermissions("read")]},
|
||||
// TODO implement missing pages: /genre, /studio & an home page.
|
||||
{path: "**", component: NotFoundComponent}
|
||||
];
|
||||
|
@ -17,12 +17,11 @@ import { AppComponent } from './app.component';
|
||||
import {ItemsGridComponent} from './components/items-grid/items-grid.component';
|
||||
import {CollectionComponent} from './pages/collection/collection.component';
|
||||
import {EpisodesListComponent} from './components/episodes-list/episodes-list.component';
|
||||
import { NotFoundComponent } from './not-found/not-found.component';
|
||||
import {NotFoundComponent} from './pages/not-found/not-found.component';
|
||||
import {PeopleListComponent} from './components/people-list/people-list.component';
|
||||
import {PlayerComponent} from './pages/player/player.component';
|
||||
import {SearchComponent} from './pages/search/search.component';
|
||||
import {ShowDetailsComponent} from './pages/show-details/show-details.component';
|
||||
import { ShowsListComponent } from './components/shows-list/shows-list.component';
|
||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||
@ -34,12 +33,11 @@ import {FallbackDirective} from "./misc/fallback.directive";
|
||||
import {AuthModule} from "./auth/auth.module";
|
||||
import {AuthRoutingModule} from "./auth/auth-routing.module";
|
||||
import {TrailerDialogComponent} from './pages/trailer-dialog/trailer-dialog.component';
|
||||
import {CollectionsListComponent} from "./collection-list/collections-list.component";
|
||||
import {ItemsListComponent} from "./components/items-list/items-list.component";
|
||||
import {MetadataEditComponent} from './pages/metadata-edit/metadata-edit.component';
|
||||
import {MatChipsModule} from "@angular/material/chips";
|
||||
import {MatAutocompleteModule} from "@angular/material/autocomplete";
|
||||
import {MatExpansionModule} from "@angular/material/expansion";
|
||||
import {ShowGridComponent} from './components/show-grid/show-grid.component';
|
||||
import {InfiniteScrollModule} from "ngx-infinite-scroll";
|
||||
|
||||
|
||||
@ -54,13 +52,11 @@ import {InfiniteScrollModule} from "ngx-infinite-scroll";
|
||||
CollectionComponent,
|
||||
SearchComponent,
|
||||
PeopleListComponent,
|
||||
ShowsListComponent,
|
||||
PasswordValidator,
|
||||
FallbackDirective,
|
||||
TrailerDialogComponent,
|
||||
CollectionsListComponent,
|
||||
ItemsListComponent,
|
||||
MetadataEditComponent,
|
||||
ShowGridComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -1,10 +0,0 @@
|
||||
<div class="scroll-row mb-5">
|
||||
<div class="collections-container" #scrollView (scroll)="onScroll()">
|
||||
<a class="collection" *ngFor="let collection of this.collections" routerLink="/collection/{{collection.slug}}" href="/collection/{{collection.slug}}">
|
||||
<div matRipple [style.background-image]="getThumb(collection.slug)"> </div>
|
||||
<p class="title">{{collection.name}}</p>
|
||||
</a>
|
||||
</div>
|
||||
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
|
||||
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
|
||||
</div>
|
@ -1,142 +0,0 @@
|
||||
@import "~bootstrap/scss/functions";
|
||||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/mixins/breakpoints";
|
||||
|
||||
.collections-container
|
||||
{
|
||||
display: flex;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
overflow-x: auto;
|
||||
min-width: 100%;
|
||||
flex-shrink: 0;
|
||||
flex-direction: row;
|
||||
|
||||
&::-webkit-scrollbar
|
||||
{
|
||||
height: 4px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb
|
||||
{
|
||||
background-color: #999;
|
||||
border-radius: 90px;
|
||||
|
||||
&:host-context(.hoverEnabled) &:hover
|
||||
{
|
||||
background-color: rgb(134, 127, 127);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collection
|
||||
{
|
||||
width: 33%;
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
list-style: none;
|
||||
padding: .5em;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
outline: none;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
@include media-breakpoint-up(sm)
|
||||
{
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md)
|
||||
{
|
||||
width: 20%;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg)
|
||||
{
|
||||
width: 18%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl)
|
||||
{
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
|
||||
&:focus, &:hover
|
||||
{
|
||||
> div
|
||||
{
|
||||
outline: solid var(--accentColor);
|
||||
}
|
||||
|
||||
> .title
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
> div
|
||||
{
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-top: 147.0588%;
|
||||
background-size: cover;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
> p
|
||||
{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
margin-bottom: 0px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:host-context(.hoverEnabled) &:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-row
|
||||
{
|
||||
position: relative;
|
||||
|
||||
&:host-context(.hoverEnabled) &:hover
|
||||
{
|
||||
.scrollBtn
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrollBtn
|
||||
{
|
||||
padding: 0;
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
bottom: 40%;
|
||||
display: none;
|
||||
|
||||
&.leftBtn
|
||||
{
|
||||
left: 0;
|
||||
padding-left: 10px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
&.rightBtn
|
||||
{
|
||||
right: 0;
|
||||
padding-right: 10px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import {Component, ElementRef, Input, ViewChild} from "@angular/core";
|
||||
import {Collection} from "../../models/collection";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
|
||||
@Component({
|
||||
selector: 'app-collections-list',
|
||||
templateUrl: './collections-list.component.html',
|
||||
styleUrls: ['./collections-list.component.scss']
|
||||
})
|
||||
export class CollectionsListComponent
|
||||
{
|
||||
@Input() collections: Collection[];
|
||||
@ViewChild("scrollView", {static: true}) private scrollView: ElementRef;
|
||||
@ViewChild("leftBtn", {static: false}) private leftBtn: MatButton;
|
||||
@ViewChild("rightBtn", {static: false}) private rightBtn: MatButton;
|
||||
|
||||
constructor(private sanitizer: DomSanitizer)
|
||||
{
|
||||
}
|
||||
|
||||
scrollLeft()
|
||||
{
|
||||
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
|
||||
this.scrollView.nativeElement.scrollBy({top: 0, left: -scroll, behavior: "smooth"});
|
||||
}
|
||||
|
||||
scrollRight()
|
||||
{
|
||||
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
|
||||
this.scrollView.nativeElement.scrollBy({top: 0, left: scroll, behavior: "smooth"});
|
||||
}
|
||||
|
||||
onScroll()
|
||||
{
|
||||
if (this.scrollView.nativeElement.scrollLeft <= 0)
|
||||
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
|
||||
else
|
||||
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
|
||||
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
|
||||
else
|
||||
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||
}
|
||||
|
||||
getThumb(slug: string)
|
||||
{
|
||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
<div class="container-fluid justify-content-center" infinite-scroll (scrolled)="this.page?.loadNext(this.client)">
|
||||
<a class="show" *ngFor="let item of this.page?.items" [href]="getLink(item)" [routerLink]="getLink(item)">
|
||||
<div matRipple [style.background-image]="getThumb(item.slug)" > </div>
|
||||
<p class="title">{{item.title}}</p>
|
||||
<p class="title">{{item.title ? item.title : item.name}}</p>
|
||||
<p class="date">{{getDate(item)}}</p>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -79,7 +79,7 @@ button
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
opacity: 1;
|
||||
|
||||
&.date
|
||||
|
@ -1,10 +1,12 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
import {ItemType, LibraryItem} from "../../../models/library-item";
|
||||
import {LibraryItem} from "../../../models/library-item";
|
||||
import {Page} from "../../../models/page";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Show, ShowRole} from "../../../models/show";
|
||||
import {Collection} from "../../../models/collection";
|
||||
import {ItemsUtils} from "../../misc/items-utils";
|
||||
|
||||
@Component({
|
||||
selector: 'app-items',
|
||||
@ -13,7 +15,7 @@ import {Show, ShowRole} from "../../../models/show";
|
||||
})
|
||||
export class ItemsGridComponent
|
||||
{
|
||||
@Input() page: Page<LibraryItem | Show | ShowRole>;
|
||||
@Input() page: Page<LibraryItem | Show | ShowRole | Collection>;
|
||||
@Input() sortEnabled: boolean = true;
|
||||
sortType: string = "title";
|
||||
sortKeys: string[] = ["title", "start year", "end year"]
|
||||
@ -34,12 +36,14 @@ export class ItemsGridComponent
|
||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||
}
|
||||
|
||||
getLink(item: LibraryItem | Show | ShowRole)
|
||||
getDate(item: LibraryItem | Show | ShowRole | Collection)
|
||||
{
|
||||
if ("type" in item && item.type == ItemType.Collection)
|
||||
return "/collection/" + item.slug;
|
||||
else
|
||||
return "/show/" + item.slug;
|
||||
return ItemsUtils.getDate(item);
|
||||
}
|
||||
|
||||
getLink(item: LibraryItem | Show | ShowRole | Collection)
|
||||
{
|
||||
return ItemsUtils.getLink(item);
|
||||
}
|
||||
|
||||
sort(type: string, order: boolean)
|
||||
@ -52,20 +56,4 @@ export class ItemsGridComponent
|
||||
this.client.get<Page<LibraryItem | Show>>(url.toString())
|
||||
.subscribe(x => this.page = Object.assign(new Page<LibraryItem | Show>(), x));
|
||||
}
|
||||
|
||||
getDate(item: LibraryItem | Show | ShowRole): string
|
||||
{
|
||||
if ("role" in item && item.role)
|
||||
{
|
||||
if ("type" in item && item.type)
|
||||
return `as ${item.role} (${item.type})`;
|
||||
return `as ${item.role}`;
|
||||
}
|
||||
if ("type" in item && item.type && typeof item.type == "string")
|
||||
return item.type;
|
||||
|
||||
if (item.endYear && item.startYear != item.endYear)
|
||||
return `${item.startYear} - ${item.endYear}`
|
||||
return item.startYear?.toString();
|
||||
}
|
||||
}
|
||||
|
11
src/app/components/items-list/items-list.component.html
Normal file
11
src/app/components/items-list/items-list.component.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="scroll-row mb-5">
|
||||
<div class="container" #scrollView (scroll)="onScroll()" infinite-scroll (scrolled)="this.items?.loadNext(this.client)" [horizontal]="true" [scrollWindow]="false">
|
||||
<a class="item" *ngFor="let item of this.items?.items" [routerLink]="getLink(item)" [href]="getLink(item)" #itemsDom>
|
||||
<div matRipple [style.background-image]="getThumb(item.slug)"> </div>
|
||||
<p class="title">{{item.title ? item.title : item.name}}</p>
|
||||
<p class="date">{{getDate(item)}}</p>
|
||||
</a>
|
||||
</div>
|
||||
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
|
||||
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
|
||||
</div>
|
@ -2,7 +2,7 @@
|
||||
@import "../../../../node_modules/bootstrap/scss/variables";
|
||||
@import "../../../../node_modules/bootstrap/scss/mixins/breakpoints";
|
||||
|
||||
.shows-container
|
||||
.container
|
||||
{
|
||||
display: flex;
|
||||
padding-left: 15px;
|
||||
@ -11,6 +11,8 @@
|
||||
min-width: 100%;
|
||||
flex-shrink: 0;
|
||||
flex-direction: row;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #999 transparent;
|
||||
|
||||
&::-webkit-scrollbar
|
||||
{
|
||||
@ -30,7 +32,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.show
|
||||
.item
|
||||
{
|
||||
width: 33%;
|
||||
min-width: 120px;
|
||||
@ -93,7 +95,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
opacity: 1;
|
||||
|
||||
&.date
|
39
src/app/components/items-list/items-list.component.ts
Normal file
39
src/app/components/items-list/items-list.component.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {Component, Input} from "@angular/core";
|
||||
import {Collection} from "../../../models/collection";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {HorizontalScroller} from "../../misc/horizontal-scroller";
|
||||
import {Page} from "../../../models/page";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Show, ShowRole} from "../../../models/show";
|
||||
import {LibraryItem} from "../../../models/library-item";
|
||||
import {ItemsUtils} from "../../misc/items-utils";
|
||||
|
||||
@Component({
|
||||
selector: 'app-items-list',
|
||||
templateUrl: './items-list.component.html',
|
||||
styleUrls: ['./items-list.component.scss']
|
||||
})
|
||||
export class ItemsListComponent extends HorizontalScroller
|
||||
{
|
||||
@Input() items: Page<Collection | Show | LibraryItem | ShowRole>;
|
||||
|
||||
constructor(private sanitizer: DomSanitizer, public client: HttpClient)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
getThumb(slug: string)
|
||||
{
|
||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||
}
|
||||
|
||||
getDate(item: LibraryItem | Show | ShowRole | Collection)
|
||||
{
|
||||
return ItemsUtils.getDate(item);
|
||||
}
|
||||
|
||||
getLink(item: LibraryItem | Show | ShowRole | Collection)
|
||||
{
|
||||
return ItemsUtils.getLink(item);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<div class="container-fluid">
|
||||
<div *ngFor="let show of this.shows" class="show-container">
|
||||
<mat-card class="show">
|
||||
<a [href]="getLink(show)" [routerLink]="getLink(show)" class="d-flex" (click)="this.clickCallback.emit(show)">
|
||||
<div class="thumb">
|
||||
<div [style.background-image]="getThumb(show)"> </div>
|
||||
</div>
|
||||
<div class="data">
|
||||
<p class="title">{{show.title}}</p>
|
||||
<p class="date" *ngIf="show.endYear && show.startYear != show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
|
||||
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
|
||||
<p class="overview">{{show.overview}}</p>
|
||||
<ul>
|
||||
<li class="provider" *ngFor="let id of this.show.externalIDs">
|
||||
<a [href]="id.link"><img [src]="id.provider.logo" [alt]="id.provider.name"/></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
@ -1,144 +0,0 @@
|
||||
@import "../../../../node_modules/bootstrap/scss/functions";
|
||||
@import "../../../../node_modules/bootstrap/scss/variables";
|
||||
@import "../../../../node_modules/bootstrap/scss/mixins/breakpoints";
|
||||
|
||||
button
|
||||
{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.arrow
|
||||
{
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.container-fluid
|
||||
{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.show-container
|
||||
{
|
||||
width: 100%;
|
||||
min-width: 300px;
|
||||
list-style: none;
|
||||
padding: .5em;
|
||||
|
||||
@include media-breakpoint-up(lg)
|
||||
{
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl)
|
||||
{
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
|
||||
.show
|
||||
{
|
||||
padding: 0;
|
||||
|
||||
> a
|
||||
{
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
outline: none;
|
||||
position: relative;
|
||||
|
||||
&:focus, &:hover
|
||||
{
|
||||
|
||||
> .data > .title
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
> .thumb
|
||||
{
|
||||
width: 33%;
|
||||
|
||||
> div
|
||||
{
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-top: 147.0588%;
|
||||
background-size: cover;
|
||||
background-color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
> .data
|
||||
{
|
||||
width: 67%;
|
||||
padding: .5rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
> p:not(.overview)
|
||||
{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
|
||||
&.date
|
||||
{
|
||||
opacity: 0.8;
|
||||
font-size: 0.8em;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
> .overview
|
||||
{
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 4rem);
|
||||
text-align: justify;
|
||||
padding-right: .5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:host-context(.hoverEnabled) &:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.provider
|
||||
{
|
||||
display: inline-block;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin: .25rem;
|
||||
|
||||
> a
|
||||
{
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
> img
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
max-width: 2.5rem;
|
||||
max-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {Show} from "../../../models/show";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-grid',
|
||||
templateUrl: './show-grid.component.html',
|
||||
styleUrls: ['./show-grid.component.scss']
|
||||
})
|
||||
export class ShowGridComponent
|
||||
{
|
||||
@Input() shows: Show[]
|
||||
@Input() externalShows: boolean = false;
|
||||
@Output() clickCallback: EventEmitter<Show> = new EventEmitter();
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) { }
|
||||
|
||||
getThumb(show: Show)
|
||||
{
|
||||
return this.sanitizer.bypassSecurityTrustStyle(`url(${show.poster})`);
|
||||
}
|
||||
|
||||
getLink(show: Show)
|
||||
{
|
||||
if (this.externalShows)
|
||||
return null;
|
||||
return `/show/${show.slug}`;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<div class="scroll-row mb-5">
|
||||
<div class="shows-container" #scrollView (scroll)="onScroll()">
|
||||
<a class="show" *ngFor="let show of this.shows" routerLink="/show/{{show.slug}}" href="/show/{{show.slug}}">
|
||||
<div matRipple [style.background-image]="getThumb(show.slug)"> </div>
|
||||
<p class="title">{{show.title}}</p>
|
||||
<p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
|
||||
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
|
||||
</a>
|
||||
</div>
|
||||
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
|
||||
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
|
||||
</div>
|
@ -1,48 +0,0 @@
|
||||
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { MatButton } from "@angular/material/button";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { Show } from "../../../models/show";
|
||||
|
||||
@Component({
|
||||
selector: 'app-shows-list',
|
||||
templateUrl: './shows-list.component.html',
|
||||
styleUrls: ['./shows-list.component.scss']
|
||||
})
|
||||
export class ShowsListComponent
|
||||
{
|
||||
@Input() shows: Show[];
|
||||
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
|
||||
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
|
||||
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) { }
|
||||
|
||||
scrollLeft()
|
||||
{
|
||||
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
|
||||
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
|
||||
}
|
||||
|
||||
scrollRight()
|
||||
{
|
||||
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
|
||||
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
|
||||
}
|
||||
|
||||
onScroll()
|
||||
{
|
||||
if (this.scrollView.nativeElement.scrollLeft <= 0)
|
||||
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
|
||||
else
|
||||
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
|
||||
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
|
||||
else
|
||||
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||
}
|
||||
|
||||
getThumb(slug: string)
|
||||
{
|
||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ export class HorizontalScroller
|
||||
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
|
||||
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
|
||||
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
|
||||
@ViewChild("itemsDom", { static: false }) private items: ElementRef;
|
||||
@ViewChild("itemsDom", { static: false }) private itemsDom: ElementRef;
|
||||
|
||||
scrollLeft()
|
||||
{
|
||||
@ -22,7 +22,7 @@ export class HorizontalScroller
|
||||
|
||||
roundScroll(offset: number): number
|
||||
{
|
||||
let itemSize: number = this.items.nativeElement.scrollWidth;
|
||||
let itemSize: number = this.itemsDom.nativeElement.scrollWidth;
|
||||
|
||||
offset = Math.round(offset / itemSize) * itemSize;
|
||||
if (offset == 0)
|
||||
|
30
src/app/misc/items-utils.ts
Normal file
30
src/app/misc/items-utils.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {ItemType, LibraryItem} from "../../models/library-item";
|
||||
import {Show, ShowRole} from "../../models/show";
|
||||
import {Collection} from "../../models/collection";
|
||||
|
||||
export class ItemsUtils
|
||||
{
|
||||
static getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
||||
{
|
||||
if ("type" in item && item.type == ItemType.Collection)
|
||||
return "/collection/" + item.slug;
|
||||
else
|
||||
return "/show/" + item.slug;
|
||||
}
|
||||
|
||||
static getDate(item: LibraryItem | Show | ShowRole | Collection): string
|
||||
{
|
||||
if ("role" in item && item.role)
|
||||
{
|
||||
if ("type" in item && item.type)
|
||||
return `as ${item.role} (${item.type})`;
|
||||
return `as ${item.role}`;
|
||||
}
|
||||
if ("type" in item && item.type && typeof item.type == "string")
|
||||
return item.type;
|
||||
|
||||
if (item.endYear && item.startYear != item.endYear)
|
||||
return `${item.startYear} - ${item.endYear}`
|
||||
return item.startYear?.toString();
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
<div *ngIf="items.collections.length > 0" class="container-fluid mt-3">
|
||||
<h3>Collections</h3>
|
||||
</div>
|
||||
<app-collections-list [collections]="items.collections"></app-collections-list>
|
||||
<app-items-list [items]="AsPage(items.collections)"></app-items-list>
|
||||
<div *ngIf="items.shows.length > 0" class="container-fluid mt-3">
|
||||
<h3>Shows</h3>
|
||||
</div>
|
||||
<app-shows-list [shows]="items.shows"></app-shows-list>
|
||||
<app-items-list [items]="AsPage(items.shows)"></app-items-list>
|
||||
<div *ngIf="items.episodes.length > 0" class="container-fluid mt-5">
|
||||
<h3>Episodes</h3>
|
||||
</div>
|
||||
<app-episodes-list displayShowTitle="true" [episodes]="items.episodes"></app-episodes-list>
|
||||
<app-episodes-list displayShowTitle="true" [episodes]="AsPage(items.episodes)"></app-episodes-list>
|
||||
<div *ngIf="items.people.length > 0" class="container-fluid mt-5">
|
||||
<h3>People</h3>
|
||||
</div>
|
||||
<app-people-list [people]="items.people"></app-people-list>
|
||||
<app-people-list [people]="AsPage(items.people)"></app-people-list>
|
||||
|
@ -2,6 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { SearchResult } from "../../../models/search-result";
|
||||
import { Title } from "@angular/platform-browser";
|
||||
import {Page} from "../../../models/page";
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
@ -36,4 +37,9 @@ export class SearchComponent implements OnInit, OnDestroy
|
||||
searchBar.classList.remove("searching");
|
||||
searchBar.value = "";
|
||||
}
|
||||
|
||||
AsPage<T>(collection: T[]): Page<T>
|
||||
{
|
||||
return new Page<T>({this: "", items: collection, next: null, count: collection.length});
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||
import { EMPTY, Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators'
|
||||
import { Collection } from "../../../models/collection";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CollectionResolverService implements Resolve<Collection>
|
||||
{
|
||||
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Collection | Observable<Collection> | Promise<Collection>
|
||||
{
|
||||
let collection: string = route.paramMap.get("collection-slug");
|
||||
return this.http.get<Collection>("api/collection/" + collection).pipe(catchError((error: HttpErrorResponse) =>
|
||||
{
|
||||
console.log(error.status + " - " + error.message);
|
||||
if (error.status == 404)
|
||||
{
|
||||
this.snackBar.open("Collection \"" + collection + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
}
|
||||
return EMPTY;
|
||||
}));
|
||||
}
|
||||
}
|
@ -4,14 +4,13 @@ import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {ActivatedRouteSnapshot, Resolve} from '@angular/router';
|
||||
import {Observable, EMPTY} from 'rxjs';
|
||||
import {catchError} from 'rxjs/operators';
|
||||
import {IResource} from "../../../models/resources/resource";
|
||||
|
||||
@Injectable()
|
||||
export class ItemResolver
|
||||
{
|
||||
public static resolvers: any[] = [];
|
||||
|
||||
static forResource<T extends IResource>(resource: string)
|
||||
static forResource<T>(resource: string)
|
||||
{
|
||||
@Injectable()
|
||||
class Resolver implements Resolve<T>
|
||||
@ -27,6 +26,16 @@ export class ItemResolver
|
||||
return this.http.get<T>(`api/${res}`)
|
||||
.pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
{
|
||||
if (error.status == 404)
|
||||
{
|
||||
this.snackBar.open(`Item not found.`, null, {
|
||||
horizontalPosition: "left",
|
||||
panelClass: ['snackError'],
|
||||
duration: 2500
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(error.status + " - " + error.message);
|
||||
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
|
||||
@ -34,6 +43,7 @@ export class ItemResolver
|
||||
panelClass: ['snackError'],
|
||||
duration: 2500
|
||||
});
|
||||
}
|
||||
return EMPTY;
|
||||
}));
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||
import { EMPTY, Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { Collection } from "../../../models/collection";
|
||||
import { People } from "../../../models/people";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PeopleResolverService implements Resolve<Collection>
|
||||
{
|
||||
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Collection | Observable<Collection> | Promise<Collection>
|
||||
{
|
||||
let people: string = route.paramMap.get("people-slug");
|
||||
return this.http.get<Collection>("api/people/" + people).pipe(catchError((error: HttpErrorResponse) =>
|
||||
{
|
||||
console.log(error.status + " - " + error.message);
|
||||
if (error.status == 404)
|
||||
{
|
||||
this.snackBar.open("People \"" + people + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
}
|
||||
return EMPTY;
|
||||
}));
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||
import { EMPTY, Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { Show } from "../../../models/show";
|
||||
|
||||
@Injectable()
|
||||
export class ShowResolverService implements Resolve<Show>
|
||||
{
|
||||
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show>
|
||||
{
|
||||
let slug: string = route.paramMap.get("show-slug");
|
||||
return this.http.get<Show>("api/shows/" + slug).pipe(catchError((error: HttpErrorResponse) =>
|
||||
{
|
||||
console.log(error.status + " - " + error.message);
|
||||
if (error.status == 404)
|
||||
this.snackBar.open("Show \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
else
|
||||
this.snackBar.open("An unknown error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||
return EMPTY;
|
||||
}));
|
||||
}
|
||||
}
|
@ -8,7 +8,10 @@ export class Page<T>
|
||||
count: number
|
||||
items: T[]
|
||||
|
||||
constructor() {}
|
||||
constructor(init?:Partial<Page<T>>)
|
||||
{
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
loadNext(client: HttpClient)
|
||||
{
|
||||
|
@ -12,6 +12,6 @@ export interface SearchResult
|
||||
shows: Show[];
|
||||
episodes: Episode[];
|
||||
people: People[];
|
||||
genrwes: Genre[];
|
||||
genres: Genre[];
|
||||
studios: Studio[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user