mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-10-26 08:12:35 -04:00
308 lines
9.3 KiB
TypeScript
308 lines
9.3 KiB
TypeScript
import { Component, Input, OnInit } from "@angular/core";
|
|
import { FormControl } from "@angular/forms";
|
|
import { ActivatedRoute, ActivatedRouteSnapshot, Params, Router } from "@angular/router";
|
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
|
import { Genre } from "../../models/resources/genre";
|
|
import { LibraryItem } from "../../models/resources/library-item";
|
|
import { Page } from "../../models/page";
|
|
import { HttpClient } from "@angular/common/http";
|
|
import { People } from "../../models/resources/people";
|
|
import { IResource } from "../../models/resources/resource";
|
|
import { Show, ShowRole } from "../../models/resources/show";
|
|
import { Collection } from "../../models/resources/collection";
|
|
import { Studio } from "../../models/resources/studio";
|
|
import { ItemsUtils } from "../../misc/items-utils";
|
|
import { PeopleService, StudioService } from "../../services/api.service";
|
|
import { PreLoaderService } from "../../services/pre-loader.service";
|
|
import { Observable } from "rxjs";
|
|
import { catchError, filter, map, mergeAll } from "rxjs/operators";
|
|
|
|
@Component({
|
|
selector: "app-items-grid",
|
|
templateUrl: "./items-grid.component.html",
|
|
styleUrls: ["./items-grid.component.scss"]
|
|
})
|
|
export class ItemsGridComponent implements OnInit
|
|
{
|
|
|
|
constructor(private route: ActivatedRoute,
|
|
private sanitizer: DomSanitizer,
|
|
private loader: PreLoaderService,
|
|
private router: Router,
|
|
private studioApi: StudioService,
|
|
private peopleApi: PeopleService,
|
|
public client: HttpClient)
|
|
{
|
|
this.route.data.subscribe((data) =>
|
|
{
|
|
this.page = data.items;
|
|
});
|
|
this.route.queryParams.subscribe((data) =>
|
|
{
|
|
this.updateGenresFilterFromQuery(data);
|
|
this.updateStudioFilterFromQuery(data);
|
|
this.updatePeopleFilterFromQuery(data);
|
|
});
|
|
this.loader.load<Genre>("/api/genres?limit=0").subscribe(data =>
|
|
{
|
|
this.genres = data;
|
|
this.updateGenresFilterFromQuery(this.route.snapshot.queryParams);
|
|
});
|
|
}
|
|
|
|
public static readonly showOnlyFilters: string[] = ["genres", "studio", "people"];
|
|
public static readonly filters: string[] = [].concat(...ItemsGridComponent.showOnlyFilters);
|
|
@Input() page: Page<LibraryItem | Show | ShowRole | Collection>;
|
|
@Input() sortEnabled: boolean = true;
|
|
|
|
complexFiltersEnabled: boolean;
|
|
|
|
sortType: string = "title";
|
|
sortKeys: string[] = ["title", "start air", "end air"];
|
|
sortUp: boolean = true;
|
|
filters: {genres: Genre[], studio: Studio, people: People[]} = {genres: [], studio: null, people: []};
|
|
|
|
genres: Genre[] = [];
|
|
|
|
studioForm: FormControl = new FormControl();
|
|
filteredStudios: Observable<Studio[]>;
|
|
|
|
peopleForm: FormControl = new FormControl();
|
|
filteredPeople: Observable<People[]>;
|
|
|
|
/*
|
|
* /browse -> /api/items | /api/shows
|
|
* /browse/:library -> /api/library/:slug/items | /api/library/:slug/shows
|
|
* /genre/:slug -> /api/shows
|
|
* /studio/:slug -> /api/shows
|
|
*
|
|
* /collection/:slug -> /api/collection/:slug/shows |> /api/collections/:slug/shows
|
|
* /people/:slug -> /api/people/:slug/roles |> /api/people/:slug/roles
|
|
*/
|
|
|
|
static routeMapper(route: ActivatedRouteSnapshot, endpoint: string, query: [string, string][]): string
|
|
{
|
|
const queryParams: [string, string][] = Object.entries(route.queryParams)
|
|
.filter(x => ItemsGridComponent.filters.includes(x[0]) || x[0] === "sortBy");
|
|
if (query)
|
|
queryParams.push(...query);
|
|
|
|
if (queryParams.some(x => ItemsGridComponent.showOnlyFilters.includes(x[0])))
|
|
endpoint = endpoint.replace(/items?$/, "show");
|
|
|
|
const params: string = queryParams.length > 0
|
|
? "?" + queryParams.map(x => `${x[0]}=${x[1]}`).join("&")
|
|
: "";
|
|
return `api/${endpoint}${params}`;
|
|
}
|
|
|
|
updateGenresFilterFromQuery(query: Params): void
|
|
{
|
|
let selectedGenres: string[] = [];
|
|
if (query.genres?.startsWith("ctn:"))
|
|
selectedGenres = query.genres.substr(4).split(",");
|
|
else if (query.genres != null)
|
|
selectedGenres = query.genres.split(",");
|
|
if (this.router.url.startsWith("/genre"))
|
|
selectedGenres.push(this.route.snapshot.params.slug);
|
|
|
|
this.filters.genres = this.genres.filter(x => selectedGenres.includes(x.slug));
|
|
}
|
|
|
|
updateStudioFilterFromQuery(query: Params): void
|
|
{
|
|
const slug: string = this.router.url.startsWith("/studio") ? this.route.snapshot.params.slug : query.studio;
|
|
|
|
if (slug && this.filters.studio?.slug !== slug)
|
|
{
|
|
this.filters.studio = {id: 0, slug, name: slug};
|
|
this.studioApi.get(slug).subscribe(x => this.filters.studio = x);
|
|
}
|
|
else if (!slug)
|
|
this.filters.studio = null;
|
|
}
|
|
|
|
updatePeopleFilterFromQuery(query: Params): void
|
|
{
|
|
let slugs: string[] = [];
|
|
if (query.people != null)
|
|
{
|
|
if (query.people.startsWith("ctn:"))
|
|
slugs = query.people.substr(4).split(",");
|
|
else
|
|
slugs = query.people.split(",");
|
|
}
|
|
else if (this.route.snapshot.params.slug && this.router.url.startsWith("/people"))
|
|
slugs = [this.route.snapshot.params.slug];
|
|
|
|
this.filters.people = slugs.map(x => ({slug: x, name: x} as People));
|
|
for (const slug of slugs)
|
|
{
|
|
this.peopleApi.get(slug).subscribe(x =>
|
|
{
|
|
const i: number = this.filters.people.findIndex(y => y.slug === slug);
|
|
this.filters.people[i] = x;
|
|
});
|
|
}
|
|
}
|
|
|
|
ngOnInit(): void
|
|
{
|
|
this.filteredStudios = this.studioForm.valueChanges
|
|
.pipe(
|
|
filter(x => x),
|
|
map(x => typeof x === "string" ? x : x.name),
|
|
map(x => this.studioApi.search(x)),
|
|
mergeAll(),
|
|
catchError(x =>
|
|
{
|
|
console.log(x);
|
|
return [];
|
|
})
|
|
);
|
|
|
|
this.filteredPeople = this.peopleForm.valueChanges
|
|
.pipe(
|
|
filter(x => x),
|
|
map(x => typeof x === "string" ? x : x.name),
|
|
map(x => this.peopleApi.search(x)),
|
|
mergeAll(),
|
|
catchError(x =>
|
|
{
|
|
console.log(x);
|
|
return [];
|
|
})
|
|
);
|
|
}
|
|
|
|
shouldDisplayNoneStudio(): boolean
|
|
{
|
|
return this.studioForm.value === "" || typeof this.studioForm.value !== "string";
|
|
}
|
|
|
|
getFilterCount(): number
|
|
{
|
|
let count: number = this.filters.genres.length + this.filters.people.length;
|
|
if (this.filters.studio != null)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
addFilter(category: string, resource: IResource, isArray: boolean = true, toggle: boolean = false): void
|
|
{
|
|
if (isArray)
|
|
{
|
|
if (this.filters[category].includes(resource) || this.filters[category].some(x => x.slug === resource.slug))
|
|
this.filters[category].splice(this.filters[category].indexOf(resource), 1);
|
|
else
|
|
this.filters[category].push(resource);
|
|
}
|
|
else
|
|
{
|
|
if (resource && (this.filters[category] === resource || this.filters[category]?.slug === resource.slug))
|
|
{
|
|
if (!toggle)
|
|
return;
|
|
this.filters[category] = null;
|
|
}
|
|
else
|
|
this.filters[category] = resource;
|
|
}
|
|
|
|
let param: string = null;
|
|
if (isArray && this.filters[category].length > 0)
|
|
param = `${this.filters[category].map(x => x.slug).join(",")}`;
|
|
else if (!isArray && this.filters[category] != null)
|
|
param = resource.slug;
|
|
|
|
if (/\/browse($|\?)/.test(this.router.url)
|
|
|| this.router.url.startsWith("/genre")
|
|
|| this.router.url.startsWith("/studio")
|
|
|| this.router.url.startsWith("/people"))
|
|
{
|
|
if (this.filters.genres.length === 1 && this.getFilterCount() === 1)
|
|
{
|
|
this.router.navigate(["genre", this.filters.genres[0].slug], {
|
|
replaceUrl: true,
|
|
queryParams: {sortBy: this.route.snapshot.queryParams.sortBy}
|
|
});
|
|
}
|
|
else if (this.filters.studio != null && this.getFilterCount() === 1)
|
|
{
|
|
this.router.navigate(["studio", this.filters.studio.slug], {
|
|
replaceUrl: true,
|
|
queryParams: {sortBy: this.route.snapshot.queryParams.sortBy}
|
|
});
|
|
}
|
|
else if (this.filters.people.length === 1 && this.getFilterCount() === 1)
|
|
{
|
|
this.router.navigate(["people", this.filters.people[0].slug], {
|
|
replaceUrl: true,
|
|
queryParams: {sortBy: this.route.snapshot.queryParams.sortBy}
|
|
});
|
|
}
|
|
else if (this.getFilterCount() === 0 || this.router.url !== "/browse")
|
|
{
|
|
const params: {[key: string]: string} = {[category]: param};
|
|
if (this.router.url.startsWith("/studio") && category !== "studio")
|
|
params.studio = this.route.snapshot.params.slug;
|
|
if (this.router.url.startsWith("/genre") && category !== "genres")
|
|
params.genres = `${this.route.snapshot.params.slug}`;
|
|
if (this.router.url.startsWith("/people") && category !== "people")
|
|
params.people = `${this.route.snapshot.params.slug}`;
|
|
|
|
this.router.navigate(["/browse"], {
|
|
queryParams: params,
|
|
replaceUrl: true,
|
|
queryParamsHandling: "merge"
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.router.navigate([], {
|
|
relativeTo: this.route,
|
|
queryParams: {[category]: param},
|
|
replaceUrl: true,
|
|
queryParamsHandling: "merge"
|
|
});
|
|
}
|
|
}
|
|
|
|
nameGetter(obj: Studio): string
|
|
{
|
|
return obj?.name ?? "None";
|
|
}
|
|
|
|
getPoster(obj: LibraryItem | Show | ShowRole | Collection): SafeStyle
|
|
{
|
|
if (!obj.poster)
|
|
return undefined;
|
|
return this.sanitizer.bypassSecurityTrustStyle(`url(${obj.poster})`);
|
|
}
|
|
|
|
getDate(item: LibraryItem | Show | ShowRole | Collection): string
|
|
{
|
|
return ItemsUtils.getDate(item);
|
|
}
|
|
|
|
getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
|
{
|
|
return ItemsUtils.getLink(item);
|
|
}
|
|
|
|
sort(type: string, order: boolean): void
|
|
{
|
|
this.sortType = type;
|
|
this.sortUp = order;
|
|
|
|
const param: string = `${this.sortType.replace(/\s/g, "")}:${this.sortUp ? "asc" : "desc"}`;
|
|
this.router.navigate([], {
|
|
relativeTo: this.route,
|
|
queryParams: { sortBy: param },
|
|
replaceUrl: true,
|
|
queryParamsHandling: "merge"
|
|
});
|
|
}
|
|
}
|