Merge branch 'develop' of https://github.com/tidusjar/ombi into develop

This commit is contained in:
TidusJar 2018-08-01 08:14:28 +01:00
commit 58af83959e
104 changed files with 2319 additions and 17403 deletions

View file

@ -1,10 +1,10 @@
#tool "nuget:?package=GitVersion.CommandLine"
#addin "Cake.Gulp"
#addin "nuget:?package=Cake.Npm&version=0.13.0"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Yarn"
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
@ -122,36 +122,19 @@ Task("SetVersionInfo")
Task("NPM")
.Does(() => {
var settings = new NpmInstallSettings {
LogLevel = NpmLogLevel.Silent,
WorkingDirectory = webProjDir,
Production = true
};
NpmInstall(settings);
Yarn.FromPath(webProjDir).Install();
});
Task("Gulp Publish")
.IsDependentOn("NPM")
.Does(() => {
var runScriptSettings = new NpmRunScriptSettings {
ScriptName="publish",
WorkingDirectory = webProjDir,
};
NpmRunScript(runScriptSettings);
Yarn.FromPath(webProjDir).RunScript("publish");
});
Task("TSLint")
.Does(() =>
{
var settings = new NpmRunScriptSettings {
WorkingDirectory = webProjDir,
ScriptName = "lint"
};
NpmRunScript(settings);
Yarn.FromPath(webProjDir).RunScript("lint");
});
Task("PrePublish")

View file

@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search
{
ProviderId = "123"
});
var search = new SearchMovieViewModel();
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);

View file

@ -15,9 +15,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> DenyChildRequest(int requestId);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId);

View file

@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
}
}

View file

@ -1,23 +0,0 @@
using System.Collections.Generic;
namespace Ombi.Core.Engine
{
public class TreeNode<T>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<T>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
public class TreeNode<T,U>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<U>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

View file

@ -202,38 +202,6 @@ namespace Ombi.Core.Engine
Collection = allRequests
};
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.Get(shouldHide.UserId)
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.Get()
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(allRequests);
}
public async Task<IEnumerable<TvRequests>> GetRequests()
{
var shouldHide = await HideFromOtherUsers();
@ -360,23 +328,6 @@ namespace Ombi.Core.Engine
return results;
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = TvRepository.Get(shouldHide.UserId);
}
else
{
allRequests = TvRepository.Get();
}
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(results);
}
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
@ -596,28 +547,6 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
{
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
foreach (var value in result)
{
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
{
Data = value,
Children = new List<TreeNode<List<ChildRequests>>>
{
new TreeNode<List<ChildRequests>>
{
Data = SortEpisodes(value.ChildRequests),
Leaf = true
}
}
});
}
return node;
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)

View file

@ -59,11 +59,6 @@ namespace Ombi.Core.Engine
return null;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
{
var result = await Search(searchTerm);
return result.Select(ParseIntoTreeNode).ToList();
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
{
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
@ -116,19 +111,6 @@ namespace Ombi.Core.Engine
return await ProcessResult(mapped);
}
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
{
var result = await GetShowInformation(tvdbid);
return ParseIntoTreeNode(result);
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
@ -136,12 +118,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
{
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{
@ -150,12 +126,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
@ -163,13 +133,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
@ -177,22 +140,6 @@ namespace Ombi.Core.Engine
return processed;
}
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{
return new TreeNode<SearchTvShowViewModel>
{
Data = result,
Children = new List<TreeNode<SearchTvShowViewModel>>
{
new TreeNode<SearchTvShowViewModel>
{
Data = result, Leaf = true
}
},
Leaf = false
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();

23
src/Ombi/.gitignore vendored
View file

@ -1,23 +1,10 @@
/wwwroot/css/**
/wwwroot/fonts/**
/wwwroot/lib/**
/wwwroot/maps/**
/wwwroot/dist/**
/wwwroot/*.js.map
/wwwroot/*.js
# dependencies
/node_modules
/bower_components
# misc
node_modules
bin
obj
wwwroot/dist
*.log
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
#/typings
/systemjs.config.js*
/Logs/**
**.db

View file

@ -4,7 +4,7 @@
"version": "2.0.0",
"tasks": [
{
"taskName": "restore",
"label": "restore",
"command": "npm",
"type": "shell",
"args": [
@ -14,7 +14,16 @@
"problemMatcher": []
},
{
"taskName": "build",
"label": "clean",
"command": "dotnet",
"type": "shell",
"args": [
"clean"
],
"problemMatcher": "$msCompile"
},
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
@ -27,7 +36,7 @@
"problemMatcher": "$msCompile"
},
{
"taskName": "lint",
"label": "lint",
"type": "shell",
"command": "npm",
"args": [

View file

@ -1,7 +1,7 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { AnimationEntryMetadata } from "@angular/core";
import { AnimationTriggerMetadata } from "@angular/animations";
export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [
export const fadeInOutAnimation: AnimationTriggerMetadata = trigger("fadeInOut", [
transition(":enter", [ // :enter is alias to 'void => *'
style({ opacity: 0 }),
animate(1000, style({ opacity: 1 })),

View file

@ -40,7 +40,7 @@ export class AppComponent implements OnInit {
__webpack_public_path__ = base + "/dist/";
}
this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no", "pl", "pt"]);
this.translate.addLangs(["en", "de", "fr", "da", "es", "it", "nl", "sv", "no", "pl", "pt"]);
// this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang("en");
@ -88,8 +88,8 @@ export class AppComponent implements OnInit {
public openMobileApp(event: any) {
event.preventDefault();
if(!this.customizationSettings.applicationUrl) {
this.notificationService.warning("Mobile","Please ask your admin to setup the Application URL!");
if (!this.customizationSettings.applicationUrl) {
this.notificationService.warning("Mobile", "Please ask your admin to setup the Application URL!");
return;
}

View file

@ -1,12 +1,12 @@
import {CommonModule, PlatformLocation} from "@angular/common";
import {HttpClient, HttpClientModule} from "@angular/common/http";
import {NgModule} from "@angular/core";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {MatButtonModule, MatCardModule, MatInputModule, MatTabsModule} from "@angular/material";
import {BrowserModule} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {RouterModule, Routes} from "@angular/router";
import { CommonModule, PlatformLocation } from "@angular/common";
import { HttpClient, HttpClientModule } from "@angular/common/http";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { HttpModule } from "@angular/http";
import { MatButtonModule, MatCardModule, MatInputModule, MatTabsModule } from "@angular/material";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule, Routes } from "@angular/router";
import { JwtModule } from "@auth0/angular-jwt";
@ -15,7 +15,7 @@ import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { CookieService } from "ng2-cookies";
import { GrowlModule } from "primeng/components/growl/growl";
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule,DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
// Components
import { AppComponent } from "./app.component";
@ -67,6 +67,14 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
return new TranslateHttpLoader(http, "/translations/", `.json?v=${version}`);
}
export function JwtTokenGetter() {
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
}
@NgModule({
imports: [
RouterModule.forRoot(routes),
@ -92,13 +100,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CommonModule,
JwtModule.forRoot({
config: {
tokenGetter: () => {
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
},
tokenGetter: JwtTokenGetter,
},
}),
TranslateModule.forRoot({

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { JwtHelperService } from "@auth0/angular-jwt";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../services";
import { ILocalUser, IUserLogin } from "./IUserLogin";
@ -53,9 +53,9 @@ export class AuthService extends ServiceHelpers {
} else {
u.roles.push(roles);
}
return <ILocalUser>u;
return <ILocalUser> u;
}
return <ILocalUser>{};
return <ILocalUser> { };
}
public hasRole(role: string): boolean {

View file

@ -11,7 +11,7 @@ export class CookieComponent implements OnInit {
public ngOnInit() {
const cookie = this.cookieService.getAll();
if(cookie.Auth) {
if (cookie.Auth) {
const jwtVal = cookie.Auth;
localStorage.setItem("id_token", jwtVal);
this.router.navigate(["search"]);

View file

@ -71,6 +71,10 @@ export interface ITvRequests {
status: string;
childRequests: IChildRequests[];
qualityOverride: number;
background: any;
totalSeasons: number;
tvDbId: number;
open: boolean; // THIS IS FOR THE UI
// For UI display
qualityOverrideTitle: string;

View file

@ -28,8 +28,16 @@ export interface ISearchTvResult {
available: boolean;
plexUrl: string;
embyUrl: string;
quality: string;
firstSeason: boolean;
latestSeason: boolean;
theTvDbId: string;
subscribed: boolean;
showSubscribe: boolean;
fullyAvailable: boolean;
partlyAvailable: boolean;
background: any;
open: boolean; // THIS IS FOR THE UI
}
export interface ITvRequestViewModel {

View file

@ -63,8 +63,8 @@ export class IssueDetailsComponent implements OnInit {
issueCategoryId: x.issueCategoryId,
subject: x.subject,
description: x.description,
status:x.status,
resolvedDate:x.resolvedDate,
status: x.status,
resolvedDate: x.resolvedDate,
title: x.title,
requestType: x.requestType,
requestId: x.requestId,
@ -117,7 +117,7 @@ export class IssueDetailsComponent implements OnInit {
} else {
this.imageService.getTvBackground(Number(issue.providerId)).subscribe(x => {
if(x) {
if (x) {
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
}

View file

@ -61,5 +61,4 @@ export class IssuesComponent implements OnInit {
this.resolvedIssues = x;
});
}
}

View file

@ -32,7 +32,7 @@ export class LoginComponent implements OnDestroy, OnInit {
public loginWithOmbi: boolean;
public get appName(): string {
if(this.customizationSettings.applicationName) {
if (this.customizationSettings.applicationName) {
return this.customizationSettings.applicationName;
} else {
return "Ombi";
@ -72,7 +72,7 @@ export class LoginComponent implements OnDestroy, OnInit {
}
});
if(authService.loggedIn()) {
if (authService.loggedIn()) {
this.router.navigate(["search"]);
}
}
@ -103,9 +103,9 @@ export class LoginComponent implements OnDestroy, OnInit {
return;
}
const value = form.value;
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false, plexTvPin: { id: 0, code: ""} };
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false, plexTvPin: { id: 0, code: "" } };
this.authService.requiresPassword(user).subscribe(x => {
if(x && this.authenticationSettings.allowNoPassword) {
if (x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password
this.authenticationSettings.allowNoPassword = false;
return;
@ -125,9 +125,9 @@ export class LoginComponent implements OnDestroy, OnInit {
}
public oauth() {
this.plexTv.GetPin(this.clientId, this.appName).subscribe(pin => {
this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => {
this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:"", plexTvPin: pin}).subscribe(x => {
this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => {
if (window.frameElement) {
// in frame
window.open(x.url, "_blank");

View file

@ -16,7 +16,6 @@ export class LoginOAuthComponent implements OnInit {
this.route.params
.subscribe((params: any) => {
this.pin = params.pin;
});
}
@ -26,7 +25,7 @@ export class LoginOAuthComponent implements OnInit {
public auth() {
this.authService.oAuth(this.pin).subscribe(x => {
if(x.access_token) {
if (x.access_token) {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
@ -34,13 +33,12 @@ export class LoginOAuthComponent implements OnInit {
return;
}
}
if(x.errorMessage) {
if (x.errorMessage) {
this.error = x.errorMessage;
}
}, err => {
this.notify.error(err.statusText);
this.router.navigate(["login"]);
});
}

View file

@ -4,7 +4,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { DomSanitizer } from "@angular/platform-browser";
import { ICustomizationSettings } from "../interfaces";
import { IdentityService, ImageService,NotificationService, SettingsService } from "../services";
import { IdentityService, ImageService, NotificationService, SettingsService } from "../services";
@Component({
templateUrl: "./resetpassword.component.html",

View file

@ -1,5 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { NguCarousel } from "@ngu/carousel";
import { NguCarouselConfig } from "@ngu/carousel";
import { ImageService, RecentlyAddedService } from "../services";
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
@ -43,7 +43,7 @@ export class RecentlyAddedComponent implements OnInit {
public groupTv: boolean = false;
// https://github.com/sheikalthaf/ngu-carousel
public carouselTile: NguCarousel;
public carouselTile: NguCarouselConfig;
constructor(private recentlyAddedService: RecentlyAddedService,
private imageService: ImageService) {}
@ -67,10 +67,10 @@ export class RecentlyAddedComponent implements OnInit {
}
public close() {
if(this.range.length < 2) {
if (this.range.length < 2) {
return;
}
if(!this.range[1]) {
if (!this.range[1]) {
// If we do not have a second date then just set it to now
this.range[1] = new Date();
}
@ -82,13 +82,13 @@ export class RecentlyAddedComponent implements OnInit {
}
private getShows() {
if(this.groupTv) {
if (this.groupTv) {
this.recentlyAddedService.getRecentlyAddedTvGrouped().subscribe(x => {
this.tv = x;
this.tv.forEach((t) => {
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
if(p) {
if (p) {
t.posterPath = p;
}
});
@ -100,7 +100,7 @@ export class RecentlyAddedComponent implements OnInit {
this.tv.forEach((t) => {
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
if(p) {
if (p) {
t.posterPath = p;
}
});
@ -114,11 +114,11 @@ export class RecentlyAddedComponent implements OnInit {
this.movies = x;
this.movies.forEach((movie) => {
if(movie.theMovieDbId) {
if (movie.theMovieDbId) {
this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => {
movie.posterPath = p;
});
} else if(movie.imdbId) {
} else if (movie.imdbId) {
this.imageService.getMoviePoster(movie.imdbId).subscribe(p => {
movie.posterPath = p;
});

View file

@ -45,8 +45,6 @@
<div>
<div *ngFor="let request of movieRequests">
<div class="row">
<div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div>

View file

@ -1,15 +1,12 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { Subject } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { NotificationService, RadarrService, RequestService } from "../services";
import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces";
import { NotificationService, RadarrService, RequestService } from "../services";
@Component({
selector: "movie-requests",
@ -45,16 +42,17 @@ export class MovieRequestsComponent implements OnInit {
private currentlyLoaded: number;
private amountToLoad: number;
constructor(private requestService: RequestService,
constructor(
private requestService: RequestService,
private auth: AuthService,
private notificationService: NotificationService,
private radarrService: RadarrService,
private sanitizer: DomSanitizer,
private readonly platformLocation: PlatformLocation) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchChanged.pipe(
debounceTime(600), // Wait Xms after the last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
@ -102,7 +100,7 @@ export class MovieRequestsComponent implements OnInit {
public changeAvailability(request: IMovieRequests, available: boolean) {
request.available = available;
if(available) {
if (available) {
this.requestService.markMovieAvailable({ id: request.id }).subscribe(x => {
if (x.result) {
this.notificationService.success(
@ -241,7 +239,7 @@ export class MovieRequestsComponent implements OnInit {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded, this.orderType, this.filter)
.subscribe(x => {
this.setOverrides(x.collection);
if(!this.movieRequests) {
if (!this.movieRequests) {
this.movieRequests = [];
}
this.movieRequests = x.collection;

View file

@ -4,7 +4,7 @@ import { IChildRequests } from "../interfaces";
import { NotificationService, RequestService } from "../services";
@Component({
selector:"tvrequests-children",
selector: "tvrequests-children",
templateUrl: "./tvrequest-children.component.html",
})
export class TvRequestChildrenComponent {
@ -26,12 +26,12 @@ export class TvRequestChildrenComponent {
public changeAvailability(request: IChildRequests, available: boolean) {
request.available = available;
request.seasonRequests.forEach((season)=> {
season.episodes.forEach((ep)=> {
request.seasonRequests.forEach((season) => {
season.episodes.forEach((ep) => {
ep.available = available;
});
});
if(available) {
if (available) {
this.requestService.markTvAvailable({ id: request.id }).subscribe(x => {
if (x.result) {
this.notificationService.success(

View file

@ -4,59 +4,41 @@
</div>
</div>
<br />
<!--TODO: I believe this +1 is causing off by one error skipping loading of tv shows
When removed and scrolling very slowly everything works as expected, however
if you scroll really quickly then you start getting duplicates of movies
since it's async and some subsequent results return first and then incrementer
is increased so you see movies which had already been gotten show up...
Removing infinte-scroll and setting max to 1000 till we work out some sort of fix
-->
<!--<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">-->
<div>
<p-treeTable [value]="tvRequests">
<p-column>
<ng-template let-col let-node="rowData" pTemplate="header">
Results
</ng-template>
<ng-template let-col let-node="rowData" pTemplate="body">
<div>
<div *ngFor="let node of tvRequests.collection">
<!--This is the section that holds the parent level results set-->
<div *ngIf="!node.leaf">
<div>
<div class="row">
<div class="myBg backdrop" [style.background-image]="node?.data?.background"></div>
<div class="myBg backdrop" [style.background-image]="node?.background"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="col-sm-2 small-padding" >
<div class="col-sm-2 small-padding">
<img class="img-responsive poster" src="{{node.data.posterPath || null}}" alt="poster">
<img class="img-responsive poster" src="{{node.posterPath || null}}" alt="poster">
</div>
<div class="col-sm-5 small-padding">
<div>
<a href="http://www.imdb.com/title/{{node.data.imdbId}}/" target="_blank">
<h4 class="request-title">{{node.data.title}} ({{node.data.releaseDate | date: 'yyyy'}})</h4>
<a href="http://www.imdb.com/title/{{node.imdbId}}/" target="_blank">
<h4 class="request-title">{{node.title}} ({{node.releaseDate | date: 'yyyy'}})</h4>
</a>
</div>
<br />
<div>
<span>Status: </span>
<span class="label label-success">{{node.data.status}}</span>
<span class="label label-success">{{node.status}}</span>
</div>
<div>Release Date: {{node.data.releaseDate | date}}</div>
<div>Release Date: {{node.releaseDate | date}}</div>
<div *ngIf="isAdmin">
<div *ngIf="node.data.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<span>{{node.data.qualityOverrideTitle}} </span>
<div *ngIf="node.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<span>{{node.qualityOverrideTitle}} </span>
</div>
<div *ngIf="node.data.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{node.data.rootPathOverrideTitle}} </span>
<div *ngIf="node.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{node.rootPathOverrideTitle}} </span>
</div>
</div>
@ -64,7 +46,8 @@
</div>
<div class="col-sm-3 col-sm-push-3 small-padding">
<button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab($event)"><i class="fa fa-plus"></i> View</button>
<button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab(node,$event)">
<i class="fa fa-plus"></i> View</button>
<div *ngIf="isAdmin">
<!--Sonarr Root Folder-->
<div *ngIf="sonarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
@ -77,7 +60,7 @@
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of sonarrRootFolders">
<a href="#" (click)="selectRootFolder(node.data, folder, $event)">{{folder.path}}</a>
<a href="#" (click)="selectRootFolder(node, folder, $event)">{{folder.path}}</a>
</li>
</ul>
</div>
@ -93,35 +76,39 @@
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of sonarrProfiles">
<a href="#" (click)="selectQualityProfile(node.data, profile, $event)">{{profile.name}}</a>
<a href="#" (click)="selectQualityProfile(node, profile, $event)">{{profile.name}}</a>
</li>
</ul>
</div>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issueBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, node.data)">{{cat.value}}</a></li>
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, node)">{{cat.value}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.leaf">
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin"
(requestDeleted)="childRequestDeleted($event)"></tvrequests-children>
<div *ngIf="node.open">
<tvrequests-children [childRequests]="node.childRequests" [isAdmin]="isAdmin" (requestDeleted)="childRequestDeleted($event)"></tvrequests-children>
</div>
<br/>
<br/>
</div>
</ng-template>
</p-column>
</p-treeTable>
<p-paginator [rows]="10" [totalRecords]="totalTv" (onPageChange)="paginate($event)"></p-paginator>
</div>
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report>
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title" [issueCategory]="issueCategorySelected"
[id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report>

View file

@ -1,21 +1,13 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { ImageService } from "./../services/image.service";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { Subject } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { FilterType, IIssueCategory, IPagenator, IRequestsViewModel, ISonarrProfile, ISonarrRootFolder, ITvRequests, OrderType } from "../interfaces";
import { NotificationService, RequestService, SonarrService } from "../services";
import { TreeNode } from "primeng/primeng";
import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequests } from "../interfaces";
import { ImageService } from "./../services/image.service";
@Component({
selector: "tv-requests",
@ -24,7 +16,7 @@ import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequ
})
export class TvRequestsComponent implements OnInit {
public tvRequests: TreeNode[];
public tvRequests: IRequestsViewModel<ITvRequests>;
public searchChanged = new Subject<string>();
public searchText: string;
public isAdmin: boolean;
@ -46,27 +38,28 @@ export class TvRequestsComponent implements OnInit {
private currentlyLoaded: number;
private amountToLoad: number;
constructor(private requestService: RequestService,
constructor(
private requestService: RequestService,
private auth: AuthService,
private sanitizer: DomSanitizer,
private imageService: ImageService,
private sonarrService: SonarrService,
private notificationService: NotificationService,
private readonly platformLocation: PlatformLocation) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchChanged.pipe(
debounceTime(600), // Wait Xms after the last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
return;
}
this.requestService.searchTvRequestsTree(this.searchText)
this.requestService.searchTvRequests(this.searchText)
.subscribe(m => {
this.tvRequests = m;
this.tvRequests.forEach((val) => this.loadBackdrop(val));
this.tvRequests.forEach((val) => this.setOverride(val.data));
this.tvRequests.collection = m;
this.tvRequests.collection.forEach((val) => this.loadBackdrop(val));
this.tvRequests.collection.forEach((val) => this.setOverride(val));
});
});
this.defaultPoster = "../../../images/default_tv_poster.png";
@ -76,47 +69,15 @@ export class TvRequestsComponent implements OnInit {
}
}
public openClosestTab(el: any) {
const rowclass = "undefined ng-star-inserted";
el = el.toElement || el.relatedTarget || el.target || el.srcElement;
if (el.nodeName === "BUTTON") {
const isButtonAlreadyActive = el.parentElement.querySelector(".active");
// if a Button already has Class: .active
if (isButtonAlreadyActive) {
isButtonAlreadyActive.classList.remove("active");
} else {
el.className += " active";
}
}
while (el.className !== rowclass) {
// Increment the loop to the parent node until we find the row we need
el = el.parentNode;
}
// At this point, the while loop has stopped and `el` represents the element that has
// the class you specified
// Then we loop through the children to find the caret which we want to click
const caretright = "fa-caret-right";
const caretdown = "fa-caret-down";
for (const value of el.children) {
// the caret from the ui has 2 class selectors depending on if expanded or not
// we search for both since we want to still toggle the clicking
if (value.className.includes(caretright) || value.className.includes(caretdown)) {
// Then we tell JS to click the element even though we hid it from the UI
value.click();
//Break from loop since we no longer need to continue looking
break;
}
}
public openClosestTab(node: ITvRequests,el: any) {
el.preventDefault();
node.open = !node.open;
}
public ngOnInit() {
this.amountToLoad = 10;
this.currentlyLoaded = 10;
this.tvRequests = [];
this.tvRequests = {collection:[], total:0};
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
this.loadInit();
@ -125,7 +86,7 @@ export class TvRequestsComponent implements OnInit {
public paginate(event: IPagenator) {
const skipAmount = event.first;
this.requestService.getTvRequestsTree(this.amountToLoad, skipAmount)
this.requestService.getTvRequests(this.amountToLoad, skipAmount, OrderType.RequestedDateDesc, FilterType.None, FilterType.None)
.subscribe(x => {
this.tvRequests = x;
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
@ -204,17 +165,17 @@ export class TvRequestsComponent implements OnInit {
private loadInit() {
this.requestService.getTotalTv().subscribe(x => this.totalTv = x);
this.requestService.getTvRequestsTree(this.amountToLoad, 0)
this.requestService.getTvRequests(this.amountToLoad, 0, OrderType.RequestedDateDesc, FilterType.None, FilterType.None)
.subscribe(x => {
this.tvRequests = x;
this.tvRequests.forEach((val, index) => {
this.tvRequests.collection.forEach((val, index) => {
this.setDefaults(val);
this.loadBackdrop(val);
this.setOverride(val.data);
this.setOverride(val);
});
});
if(this.isAdmin) {
if (this.isAdmin) {
this.sonarrService.getQualityProfilesWithoutSettings()
.subscribe(x => this.sonarrProfiles = x);
@ -228,20 +189,20 @@ export class TvRequestsComponent implements OnInit {
this.loadInit();
}
private setDefaults(val: any) {
if (val.data.posterPath === null) {
val.data.posterPath = this.defaultPoster;
private setDefaults(val: ITvRequests) {
if (val.posterPath === null) {
val.posterPath = this.defaultPoster;
}
}
private loadBackdrop(val: TreeNode): void {
if (val.data.background != null) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")");
private loadBackdrop(val: ITvRequests): void {
if (val.background != null) {
val.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/w1280" + val.background + ")");
} else {
this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => {
if(x) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle
this.imageService.getTvBanner(val.tvDbId).subscribe(x => {
if (x) {
val.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
}
});

View file

@ -1,11 +1,9 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { TranslateService } from "@ngx-translate/core";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { Subject } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces";
@ -32,15 +30,16 @@ export class MovieSearchComponent implements OnInit {
public issueCategorySelected: IIssueCategory;
public defaultPoster: string;
constructor(private searchService: SearchService, private requestService: RequestService,
constructor(
private searchService: SearchService, private requestService: RequestService,
private notificationService: NotificationService, private authService: AuthService,
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
private readonly platformLocation: PlatformLocation) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchChanged.pipe(
debounceTime(600), // Wait Xms after the last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();

View file

@ -1,13 +1,10 @@
import { Component, OnInit } from "@angular/core";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { Component, OnInit } from "@angular/core";
import { Subject } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { NotificationService, RequestService, SearchService } from "../services";
import { IRequestEngineResult, ISearchMovieResult, ISearchMovieResultContainer } from "../interfaces";
import { NotificationService, RequestService, SearchService } from "../services";
@Component({
selector: "movie-search-grid",
@ -22,13 +19,14 @@ export class MovieSearchGridComponent implements OnInit {
public result: IRequestEngineResult;
public searchApplied = false;
constructor(private searchService: SearchService, private requestService: RequestService,
constructor(
private searchService: SearchService, private requestService: RequestService,
private notificationService: NotificationService, private authService: AuthService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchChanged.pipe(
debounceTime(600), // Wait Xms afterthe last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();
@ -67,7 +65,7 @@ export class MovieSearchGridComponent implements OnInit {
}
try {
this.requestService.requestMovie({ theMovieDbId : searchResult.id})
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
.subscribe(x => {
this.result = x;
@ -149,15 +147,14 @@ export class MovieSearchGridComponent implements OnInit {
}
private processGrid(movies: ISearchMovieResult[]) {
let container = <ISearchMovieResultContainer>{ movies: [] };
let container = <ISearchMovieResultContainer> { movies: [] };
movies.forEach((movie, i) => {
i++;
if((i % 4) === 0) {
if ((i % 4) === 0) {
container.movies.push(movie);
this.movieResultGrid.push(container);
container = <ISearchMovieResultContainer>{ movies: [] };
container = <ISearchMovieResultContainer> { movies: [] };
} else {
container.movies.push(movie);
}
});

View file

@ -1,5 +1,4 @@
import { Component, Input, OnInit} from "@angular/core";
import "rxjs/add/operator/takeUntil";
import { NotificationService } from "../services";
import { RequestService } from "../services";
@ -39,20 +38,20 @@ export class SeriesInformationComponent implements OnInit {
});
});
if(!selected) {
if (!selected) {
this.notificationService.error("You need to select some episodes!");
return;
}
this.series.requested = true;
const viewModel = <ITvRequestViewModel>{ firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id};
const viewModel = <ITvRequestViewModel> { firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id};
viewModel.seasons = [];
this.series.seasonRequests.forEach((season) => {
const seasonsViewModel = <ISeasonsViewModel>{seasonNumber: season.seasonNumber, episodes: []};
const seasonsViewModel = <ISeasonsViewModel> {seasonNumber: season.seasonNumber, episodes: []};
season.episodes.forEach(ep => {
if(!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) {
if(ep.selected) {
if (!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) {
if (ep.selected) {
seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber});
}
}

View file

@ -42,46 +42,46 @@
<i class='fa fa-film no-search-results-icon'></i>
<div class='no-search-results-text'>{{ 'Search.NoResults' | translate }}</div>
</div>
<p-treeTable [value]="tvResults">
<p-column>
<ng-template let-col let-node="rowData" pTemplate="header">
{{ 'Search.TvShows.Results' | translate }}
</ng-template>
<ng-template let-col let-node="rowData" pTemplate="body">
<div *ngIf="tvResults" >
<div *ngFor="let node of tvResults">
<!--This is the section that holds the parent level search results set-->
<div *ngIf="!node.leaf">
<div class="row" >
<div *ngIf="node">
<div class="row">
<div class="myBg backdrop" [style.background-image]="node?.data?.background"></div>
<div class="myBg backdrop" [style.background-image]="node?.background"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="col-sm-2 small-padding">
<img *ngIf="node?.data?.banner" class="img-responsive poster" width="150" [src]="node.data.banner" alt="poster">
<img *ngIf="node.banner" class="img-responsive poster" width="150" [src]="node.banner" alt="poster">
</div>
<div class="col-sm-8 small-padding">
<div>
<a *ngIf="node.data.imdbId" href="{{node.data.imdbId}}" target="_blank">
<h4>{{node.data.title}} ({{node.data.firstAired | date: 'yyyy'}})</h4>
<a *ngIf="node.imdbId" href="{{node.imdbId}}" target="_blank">
<h4>{{node.title}} ({{node.firstAired | date: 'yyyy'}})</h4>
</a>
<span class="tags">
<a *ngIf="node.data.homepage" id="homepageLabel" href="{{node.data.homepage}}" target="_blank"><span class="label label-info" >{{ 'Search.Movies.HomePage' | translate }}</span></a>
<a *ngIf="node.homepage" id="homepageLabel" href="{{node.homepage}}" target="_blank">
<span class="label label-info">{{ 'Search.Movies.HomePage' | translate }}</span>
</a>
<a *ngIf="node.data.trailer" id="trailerLabel" href="{{node.data.trailer}}" target="_blank"><span class="label label-info">{{ 'Search.Movies.Trailer' | translate }}</span></a>
<a *ngIf="node.trailer" id="trailerLabel" href="{{node.trailer}}" target="_blank">
<span class="label label-info">{{ 'Search.Movies.Trailer' | translate }}</span>
</a>
<span *ngIf="node.data.status" class="label label-primary" id="statusLabel" target="_blank">{{node.data.status}}</span>
<span *ngIf="node.status" class="label label-primary" id="statusLabel" target="_blank">{{node.status}}</span>
<span *ngIf="node.data.firstAired" class="label label-info" target="_blank" id="airDateLabel">{{ 'Search.TvShows.AirDate' | translate }} {{node.data.firstAired | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="node.firstAired" class="label label-info" target="_blank" id="airDateLabel">{{ 'Search.TvShows.AirDate' | translate }} {{node.firstAired | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="node.data.network" class="label label-info" id="networkLabel" target="_blank">{{node.data.network}}</span>
<span *ngIf="node.network" class="label label-info" id="networkLabel" target="_blank">{{node.network}}</span>
<span *ngIf="node.data.available" class="label label-success" id="availableLabel">{{ 'Common.Available' | translate }}</span>
<span *ngIf="node.available" class="label label-success" id="availableLabel">{{ 'Common.Available' | translate }}</span>
<span *ngIf="node.data.partlyAvailable" class="label label-warning" id="partiallyAvailableLabel">{{ 'Common.PartlyAvailable' | translate }}</span>
<span *ngIf="node.partlyAvailable" class="label label-warning" id="partiallyAvailableLabel">{{ 'Common.PartlyAvailable' | translate }}</span>
@ -90,47 +90,45 @@
<br />
<br />
</div>
<p class="tv-overview">{{node.data.overview}}</p>
<p class="tv-overview">{{node.overview}}</p>
</div>
<div class="col-sm-2 small-padding">
<div *ngIf="!node.data.fullyAvailable" class="dropdown">
<div *ngIf="!node.fullyAvailable" class="dropdown">
<button class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Common.Request' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li>
<a href="#" (click)="allSeasons(node.data, $event)">{{ 'Search.TvShows.AllSeasons' | translate }}</a>
<a href="#" (click)="allSeasons(node, $event)">{{ 'Search.TvShows.AllSeasons' | translate }}</a>
</li>
<li>
<a href="#" (click)="firstSeason(node.data, $event)">{{ 'Search.TvShows.FirstSeason' | translate }}</a>
<a href="#" (click)="firstSeason(node, $event)">{{ 'Search.TvShows.FirstSeason' | translate }}</a>
</li>
<li>
<a href="#" (click)="latestSeason(node.data, $event)">{{ 'Search.TvShows.LatestSeason' | translate }}</a>
<a href="#" (click)="latestSeason(node, $event)">{{ 'Search.TvShows.LatestSeason' | translate }}</a>
</li>
<li>
<a href="#" (click)="openClosestTab($event)">{{ 'Search.TvShows.Select' | translate }}</a>
<a href="#" (click)="openClosestTab(node, $event)">{{ 'Search.TvShows.Select' | translate }}</a>
</li>
</ul>
</div>
<div *ngIf="node.data.fullyAvailable">
<div *ngIf="node.fullyAvailable">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}
</button>
</div>
<br />
<div *ngIf="node.data.plexUrl && node.data.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}"
target="_blank">
<div *ngIf="node.plexUrl && node.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.plexUrl}}" target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnPlex' | translate }}
</a>
</div>
<div *ngIf="node.data.embyUrl && node.data.available">
<a style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
target="_blank">
<div *ngIf="node.embyUrl && node.available">
<a style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{node.embyUrl}}" target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
</a>
</div>
@ -142,11 +140,11 @@
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, node.data)">{{cat.value}}</a>
<a [routerLink]="" (click)="reportIssue(cat, node)">{{cat.value}}</a>
</li>
</ul>
</div>
<div *ngIf="!node.data.available">
<div *ngIf="!node.available">
<br />
<br />
</div>
@ -156,15 +154,13 @@
</div>
</div>
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.leaf">
<seriesinformation [seriesId]="node.data.id"></seriesinformation>
<div *ngIf="node.open">
<seriesinformation [seriesId]="node.id"></seriesinformation>
</div>
<br/>
<br/>
</ng-template>
</p-column>
</p-treeTable>
</div>
</div>
</div>

View file

@ -1,15 +1,13 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { Subject } from "rxjs/Subject";
import { Subject } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { IIssueCategory, IRequestEngineResult, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
import { ImageService, NotificationService, RequestService, SearchService } from "../services";
import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces";
import { IIssueCategory, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
@Component({
selector: "tv-search",
templateUrl: "./tvsearch.component.html",
@ -19,7 +17,7 @@ export class TvSearchComponent implements OnInit {
public searchText: string;
public searchChanged = new Subject<string>();
public tvResults: TreeNode[];
public tvResults: ISearchTvResult[];
public result: IRequestEngineResult;
public searchApplied = false;
public defaultPoster: string;
@ -32,21 +30,22 @@ export class TvSearchComponent implements OnInit {
public issueProviderId: string;
public issueCategorySelected: IIssueCategory;
constructor(private searchService: SearchService, private requestService: RequestService,
constructor(
private searchService: SearchService, private requestService: RequestService,
private notificationService: NotificationService, private authService: AuthService,
private imageService: ImageService, private sanitizer: DomSanitizer,
private readonly platformLocation: PlatformLocation) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchChanged.pipe(
debounceTime(600), // Wait Xms after the last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();
return;
}
this.searchService.searchTvTreeNode(this.searchText)
this.searchService.searchTv(this.searchText)
.subscribe(x => {
this.tvResults = x;
this.searchApplied = true;
@ -55,34 +54,13 @@ export class TvSearchComponent implements OnInit {
});
this.defaultPoster = "../../../images/default_tv_poster.png";
const base = this.platformLocation.getBaseHrefFromDOM();
if(base) {
if (base) {
this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png";
}
}
public openClosestTab(el: any) {
public openClosestTab(node: ISearchTvResult,el: any) {
el.preventDefault();
const rowclass = "undefined ng-star-inserted";
el = el.toElement || el.relatedTarget || el.target;
while (el.className !== rowclass) {
// Increment the loop to the parent node until we find the row we need
el = el.parentNode;
}
// At this point, the while loop has stopped and `el` represents the element that has
// the class you specified
// Then we loop through the children to find the caret which we want to click
const caretright = "fa-caret-right";
const caretdown = "fa-caret-down";
for (const value of el.children) {
// the caret from the ui has 2 class selectors depending on if expanded or not
// we search for both since we want to still toggle the clicking
if (value.className.includes(caretright) || value.className.includes(caretdown)) {
// Then we tell JS to click the element even though we hid it from the UI
value.click();
//Break from loop since we no longer need to continue looking
break;
}
}
node.open = !node.open;
}
public ngOnInit() {
@ -91,7 +69,7 @@ export class TvSearchComponent implements OnInit {
this.result = {
message: "",
result: false,
errorMessage:"",
errorMessage: "",
};
this.popularShows();
}
@ -138,16 +116,16 @@ export class TvSearchComponent implements OnInit {
public getExtraInfo() {
this.tvResults.forEach((val, index) => {
this.imageService.getTvBanner(val.data.id).subscribe(x => {
if(x) {
val.data.background = this.sanitizer.
this.imageService.getTvBanner(val.id).subscribe(x => {
if (x) {
val.background = this.sanitizer.
bypassSecurityTrustStyle
("url(" + x + ")");
}
});
this.searchService.getShowInformationTreeNode(val.data.id)
this.searchService.getShowInformation(val.id)
.subscribe(x => {
if (x.data) {
if (x) {
this.setDefaults(x);
this.updateItem(val, x);
} else {
@ -166,14 +144,14 @@ export class TvSearchComponent implements OnInit {
searchResult.approved = true;
}
const viewModel = <ITvRequestViewModel>{ firstSeason: searchResult.firstSeason, latestSeason: searchResult.latestSeason, requestAll: searchResult.requestAll, tvDbId: searchResult.id};
const viewModel = <ITvRequestViewModel> { firstSeason: searchResult.firstSeason, latestSeason: searchResult.latestSeason, requestAll: searchResult.requestAll, tvDbId: searchResult.id };
viewModel.seasons = [];
searchResult.seasonRequests.forEach((season) => {
const seasonsViewModel = <ISeasonsViewModel>{seasonNumber: season.seasonNumber, episodes: []};
const seasonsViewModel = <ISeasonsViewModel> { seasonNumber: season.seasonNumber, episodes: [] };
season.episodes.forEach(ep => {
if(!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) {
if(ep.requested) {
seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber});
if (!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) {
if (ep.requested) {
seasonsViewModel.episodes.push({ episodeNumber: ep.episodeNumber });
}
}
});
@ -223,29 +201,29 @@ export class TvSearchComponent implements OnInit {
this.issueProviderId = req.id.toString();
}
private updateItem(key: TreeNode, updated: TreeNode) {
private updateItem(key: ISearchTvResult, updated: ISearchTvResult) {
const index = this.tvResults.indexOf(key, 0);
if (index > -1) {
// Update certain properties, otherwise we will loose some data
this.tvResults[index].data.title = updated.data.title;
this.tvResults[index].data.banner = updated.data.banner;
this.tvResults[index].data.imdbId = updated.data.imdbId;
this.tvResults[index].data.seasonRequests = updated.data.seasonRequests;
this.tvResults[index].data.seriesId = updated.data.seriesId;
this.tvResults[index].data.fullyAvailable = updated.data.fullyAvailable;
this.tvResults[index].data.backdrop = updated.data.backdrop;
this.tvResults[index].title = updated.title;
this.tvResults[index].banner = updated.banner;
this.tvResults[index].imdbId = updated.imdbId;
this.tvResults[index].seasonRequests = updated.seasonRequests;
this.tvResults[index].seriesId = updated.seriesId;
this.tvResults[index].fullyAvailable = updated.fullyAvailable;
this.tvResults[index].background = updated.banner;
}
}
private setDefaults(x: any) {
if (x.data.banner === null) {
x.data.banner = this.defaultPoster;
private setDefaults(x: ISearchTvResult) {
if (x.banner === null) {
x.banner = this.defaultPoster;
}
if (x.data.imdbId === null) {
x.data.imdbId = "https://www.tvmaze.com/shows/" + x.data.seriesId;
if (x.imdbId === null) {
x.imdbId = "https://www.tvmaze.com/shows/" + x.seriesId;
} else {
x.data.imdbId = "http://www.imdb.com/title/" + x.data.imdbId + "/";
x.imdbId = "http://www.imdb.com/title/" + x.imdbId + "/";
}
}

View file

@ -1,7 +1,7 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers";

View file

@ -1,7 +1,7 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers";

View file

@ -1,12 +1,12 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers";
import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel,IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces";
import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces";
@Injectable()
export class PlexService extends ServiceHelpers {

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers";

View file

@ -2,7 +2,7 @@
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IPlexPin } from "../../interfaces";
@ -10,11 +10,10 @@ import { IPlexPin } from "../../interfaces";
export class PlexTvService {
constructor(private http: HttpClient, public platformLocation: PlatformLocation) {
}
public GetPin(clientId: string, applicationName: string): Observable<IPlexPin> {
const headers = new HttpHeaders({"Content-Type":"application/json",
const headers = new HttpHeaders({"Content-Type": "application/json",
"X-Plex-Client-Identifier": clientId,
"X-Plex-Product": applicationName,
"X-Plex-Version": "3",

View file

@ -1,7 +1,7 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
import { IRadarrSettings } from "../../interfaces";

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ISonarrSettings } from "../../interfaces";
import { ISonarrProfile, ISonarrRootFolder } from "../../interfaces";

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers";
@ -37,6 +37,7 @@ export class TesterService extends ServiceHelpers {
public pushbulletTest(settings: IPushbulletNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}pushbullet`, JSON.stringify(settings), {headers: this.headers});
}
public pushoverTest(settings: IPushoverNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}pushover`, JSON.stringify(settings), {headers: this.headers});
}
@ -80,9 +81,11 @@ export class TesterService extends ServiceHelpers {
public sickrageTest(settings: ISickRageSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}sickrage`, JSON.stringify(settings), {headers: this.headers});
}
public newsletterTest(settings: INewsletterNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers});
}
public mobileNotificationTest(settings: IMobileNotificationTestSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}mobile`, JSON.stringify(settings), {headers: this.headers});
}

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ICheckbox, ICreateWizardUser, IIdentityResult, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";

View file

@ -1,6 +1,6 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { HttpClient } from "@angular/common/http";

View file

@ -1,10 +1,10 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IIssueCategory, IIssueComments,IIssueCount, IIssues, IIssuesChat, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces";
import { IIssueCategory, IIssueComments, IIssueCount, IIssues, IIssuesChat, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@Injectable()

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "./service.helpers";

View file

@ -1,6 +1,6 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { HttpClient } from "@angular/common/http";

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IMobileUsersViewModel } from "./../interfaces";
import { ServiceHelpers } from "./service.helpers";

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IMassEmailModel } from "./../interfaces";

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
import { ServiceHelpers } from "./service.helpers";
@ -19,6 +19,7 @@ export class RecentlyAddedService extends ServiceHelpers {
public getRecentlyAddedTv(): Observable<IRecentlyAddedTvShows[]> {
return this.http.get<IRecentlyAddedTvShows[]>(`${this. url}tv/`, {headers: this.headers});
}
public getRecentlyAddedTvGrouped(): Observable<IRecentlyAddedTvShows[]> {
return this.http.get<IRecentlyAddedTvShows[]>(`${this.url}tv/grouped`, {headers: this.headers});
}

View file

@ -1,12 +1,11 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces";
import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestsViewModel, ITvRequests,ITvUpdateModel, OrderType } from "../interfaces";
import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces";
import { ITvRequestViewModel } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@ -64,8 +63,8 @@ export class RequestService extends ServiceHelpers {
return this.http.put<IMovieRequests>(`${this.url}movie/`, JSON.stringify(request), {headers: this.headers});
}
public getTvRequests(count: number, position: number): Observable<ITvRequests[]> {
return this.http.get<ITvRequests[]>(`${this.url}tv/${count}/${position}`, {headers: this.headers});
public getTvRequests(count: number, position: number, order: OrderType, status: FilterType, availability: FilterType): Observable<IRequestsViewModel<ITvRequests>> {
return this.http.get<IRequestsViewModel<ITvRequests>>(`${this.url}tv/${count}/${position}/${order}/${status}/${availability}`, {headers: this.headers});
}
public getTvRequestsTree(count: number, position: number): Observable<TreeNode[]> {

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { TreeNode } from "primeng/primeng";
import { ISearchMovieResult } from "../interfaces";
@ -56,16 +56,16 @@ export class SearchService extends ServiceHelpers {
return this.http.get<ISearchTvResult>(`${this.url}/Tv/info/${theTvDbId}`, {headers: this.headers});
}
public popularTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/popular/tree`, {headers: this.headers});
public popularTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/popular`, {headers: this.headers});
}
public mostWatchedTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/mostwatched/tree`, {headers: this.headers});
public mostWatchedTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/mostwatched`, {headers: this.headers});
}
public anticipatedTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/anticipated/tree`, {headers: this.headers});
public anticipatedTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/anticipated`, {headers: this.headers});
}
public trendingTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/trending/tree`, {headers: this.headers});
public trendingTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/trending`, {headers: this.headers});
}
}

View file

@ -1,6 +1,5 @@
import { PlatformLocation } from "@angular/common";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import "rxjs/add/observable/throw";
export class ServiceHelpers {

View file

@ -1,7 +1,7 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import {
IAbout,

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "./service.helpers";

View file

@ -25,13 +25,13 @@ export class CustomizationComponent implements OnInit {
return item.fullName === this.settings.presetThemeName;
})[0];
if(existingTheme) {
if (existingTheme) {
const index = this.themes.indexOf(existingTheme, 0);
if (index > -1) {
this.themes.splice(index, 1);
}
}
if(x.hasPresetTheme) {
if (x.hasPresetTheme) {
this.themes.unshift({displayName: x.presetThemeDisplayName, fullName: x.presetThemeName, url: existingTheme.url, version: x.presetThemeVersion});
this.themes.unshift({displayName: "None", fullName: "None", url: "", version: ""});
} else {
@ -45,8 +45,8 @@ export class CustomizationComponent implements OnInit {
public save() {
this.settingsService.verifyUrl(this.settings.applicationUrl).subscribe(x => {
if(this.settings.applicationUrl) {
if(!x) {
if (this.settings.applicationUrl) {
if (!x) {
this.notificationService.error(`The URL "${this.settings.applicationUrl}" is not valid. Please format it correctly e.g. http://www.google.com/`);
return;
}
@ -64,16 +64,16 @@ export class CustomizationComponent implements OnInit {
}
public dropDownChange(event: any): void {
const selectedThemeFullName = <string>event.target.value;
const selectedThemeFullName = <string> event.target.value;
const selectedTheme = this.themes.filter((val) => {
return val.fullName === selectedThemeFullName;
})[0];
if(selectedTheme.fullName === this.settings.presetThemeName) {
if (selectedTheme.fullName === this.settings.presetThemeName) {
return;
}
if(selectedTheme.fullName === "None" || selectedTheme.fullName === "-1") {
if (selectedTheme.fullName === "None" || selectedTheme.fullName === "-1") {
this.settings.presetThemeName = "";
this.settings.presetThemeContent = "";
return;

View file

@ -39,7 +39,7 @@ export class DiscordComponent implements OnInit {
return;
}
const settings = <IDiscordNotifcationSettings>form.value;
const settings = <IDiscordNotifcationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveDiscordNotificationSettings(settings).subscribe(x => {

View file

@ -54,7 +54,7 @@ export class EmailNotificationComponent implements OnInit {
return;
}
const settings = <IEmailNotificationSettings>form.value;
const settings = <IEmailNotificationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveEmailNotificationSettings(settings).subscribe(x => {

View file

@ -29,7 +29,7 @@ export class MattermostComponent implements OnInit {
username: [x.username],
webhookUrl: [x.webhookUrl, [Validators.required]],
channel: [x.channel],
iconUrl:[x.iconUrl],
iconUrl: [x.iconUrl],
});
});
@ -41,7 +41,7 @@ export class MattermostComponent implements OnInit {
return;
}
const settings = <IMattermostNotifcationSettings>form.value;
const settings = <IMattermostNotifcationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveMattermostNotificationSettings(settings).subscribe(x => {

View file

@ -32,9 +32,9 @@ export class MobileComponent implements OnInit {
});
this.mobileService.getUserDeviceList().subscribe(x => {
if(x.length <= 0) {
if (x.length <= 0) {
this.userList = [];
this.userList.push({username:"None",devices:0, userId:""});
this.userList.push({username: "None", devices: 0, userId: ""});
} else {
this.userList = x;
}
@ -47,7 +47,7 @@ export class MobileComponent implements OnInit {
return;
}
const settings = <IMobileNotifcationSettings>form.value;
const settings = <IMobileNotifcationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveMobileNotificationSettings(settings).subscribe(x => {
@ -65,8 +65,8 @@ export class MobileComponent implements OnInit {
this.notificationService.error("Please check your entered values");
return;
}
if(!this.testUserId) {
this.notificationService.warning("Warning","Please select a user to send the test notification");
if (!this.testUserId) {
this.notificationService.warning("Warning", "Please select a user to send the test notification");
return;
}

View file

@ -51,10 +51,10 @@ export class NewsletterComponent implements OnInit {
}
public addEmail() {
if(this.emailToAdd) {
if (this.emailToAdd) {
const emailRegex = "[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}";
const match = this.emailToAdd.match(emailRegex)!;
if(match && match.length > 0) {
if (match && match.length > 0) {
this.settings.externalEmails.push(this.emailToAdd);
this.emailToAdd = "";
} else {

View file

@ -37,7 +37,7 @@ export class PushbulletComponent implements OnInit {
return;
}
const settings = <IPushbulletNotificationSettings>form.value;
const settings = <IPushbulletNotificationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.savePushbulletNotificationSettings(settings).subscribe(x => {

View file

@ -37,7 +37,7 @@ export class PushoverComponent implements OnInit {
return;
}
const settings = <IPushoverNotificationSettings>form.value;
const settings = <IPushoverNotificationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.savePushoverNotificationSettings(settings).subscribe(x => {

View file

@ -41,7 +41,7 @@ export class SlackComponent implements OnInit {
return;
}
const settings = <ISlackNotificationSettings>form.value;
const settings = <ISlackNotificationSettings> form.value;
if (settings.iconEmoji && settings.iconUrl) {
this.notificationService.error("You cannot have a Emoji icon and a URL icon");
@ -65,7 +65,7 @@ export class SlackComponent implements OnInit {
return;
}
const settings = <ISlackNotificationSettings>form.value;
const settings = <ISlackNotificationSettings> form.value;
if (settings.iconEmoji && settings.iconUrl) {
this.notificationService.error("You cannot have a Emoji icon and a URL icon");

View file

@ -40,7 +40,7 @@ export class TelegramComponent implements OnInit {
return;
}
const settings = <ITelegramNotifcationSettings>form.value;
const settings = <ITelegramNotifcationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveTelegramNotificationSettings(settings).subscribe(x => {

View file

@ -41,9 +41,9 @@ export class OmbiComponent implements OnInit {
return;
}
const result = <IOmbiSettings>form.value;
if(result.baseUrl && result.baseUrl.length > 0) {
if(!result.baseUrl.startsWith("/")) {
const result = <IOmbiSettings> form.value;
if (result.baseUrl && result.baseUrl.length > 0) {
if (!result.baseUrl.startsWith("/")) {
this.notificationService.error("Please ensure your base url starts with a '/'");
return;
}

View file

@ -1,10 +1,8 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import "rxjs/add/operator/takeUntil";
import { Subject } from "rxjs/Subject";
import { IPlexServerResponse, IPlexServerViewModel } from "../../interfaces";
import { IPlexLibrariesSettings, IPlexServer, IPlexSettings } from "../../interfaces";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { IPlexLibrariesSettings, IPlexServer, IPlexServerResponse, IPlexServerViewModel, IPlexSettings } from "../../interfaces";
import { JobService, NotificationService, PlexService, SettingsService, TesterService } from "../../services";
@Component({
@ -21,7 +19,8 @@ export class PlexComponent implements OnInit, OnDestroy {
private subscriptions = new Subject<void>();
constructor(private settingsService: SettingsService,
constructor(
private settingsService: SettingsService,
private notificationService: NotificationService,
private plexService: PlexService,
private testerService: TesterService,
@ -34,9 +33,9 @@ export class PlexComponent implements OnInit, OnDestroy {
}
public requestServers(server: IPlexServer) {
this.plexService.getServers(this.username, this.password)
.takeUntil(this.subscriptions)
.subscribe(x => {
this.plexService.getServers(this.username, this.password).pipe(
takeUntil(this.subscriptions),
).subscribe(x => {
if (x.success) {
this.loadedServers = x;
this.serversButton = true;
@ -72,7 +71,7 @@ export class PlexComponent implements OnInit, OnDestroy {
if (this.settings.servers == null) {
this.settings.servers = [];
}
this.settings.servers.push(<IPlexServer>{ name: "New*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) });
this.settings.servers.push(<IPlexServer> { name: "New*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) });
}
@ -120,7 +119,7 @@ export class PlexComponent implements OnInit, OnDestroy {
public runCacher(): void {
this.jobService.runPlexCacher().subscribe(x => {
if(x) {
if (x) {
this.notificationService.success("Triggered the Plex Full Sync");
}
});
@ -128,7 +127,7 @@ export class PlexComponent implements OnInit, OnDestroy {
public runRecentlyAddedCacher(): void {
this.jobService.runPlexRecentlyAddedCacher().subscribe(x => {
if(x) {
if (x) {
this.notificationService.success("Triggered the Plex Recently Added Sync");
}
});

View file

@ -93,7 +93,7 @@ export class RadarrComponent implements OnInit {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <IRadarrSettings>form.value;
const settings = <IRadarrSettings> form.value;
this.testerService.radarrTest(settings).subscribe(x => {
if (x === true) {
this.notificationService.success("Successfully connected to Radarr!");
@ -108,12 +108,12 @@ public onSubmit(form: FormGroup) {
this.notificationService.error("Please check your entered values");
return;
}
if(form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") {
if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <IRadarrSettings>form.value;
const settings = <IRadarrSettings> form.value;
this.settingsService.saveRadarr(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully saved Radarr settings");

View file

@ -1,14 +1,16 @@
import { CommonModule } from "@angular/common";
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule, Routes } from "@angular/router";
import { NgbAccordionModule, NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { ClipboardModule } from "ngx-clipboard/dist";
import { ClipboardModule } from "ngx-clipboard";
import { AuthGuard } from "../auth/auth.guard";
import { AuthService } from "../auth/auth.service";
import { CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService,
SonarrService, TesterService, ValidationService } from "../services";
import {
CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService,
SonarrService, TesterService, ValidationService,
} from "../services";
import { PipeModule } from "../pipes/pipe.module";
import { AboutComponent } from "./about/about.component";

View file

@ -40,7 +40,7 @@ export class SickRageComponent implements OnInit {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <ISickRageSettings>form.value;
const settings = <ISickRageSettings> form.value;
this.testerService.sickrageTest(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully connected to SickRage!");

View file

@ -93,7 +93,7 @@ export class SonarrComponent implements OnInit {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <ISonarrSettings>form.value;
const settings = <ISonarrSettings> form.value;
this.testerService.sonarrTest(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully connected to Sonarr!");
@ -108,13 +108,13 @@ export class SonarrComponent implements OnInit {
this.notificationService.error("Please check your entered values");
return;
}
if(form.controls.defaultQualityProfile) {
if(form.controls.defaultQualityProfile.value === "-1") {
if (form.controls.defaultQualityProfile) {
if (form.controls.defaultQualityProfile.value === "-1") {
this.notificationService.error("Please check your entered values");
}
}
if(form.controls.defaultRootPath) {
if(form.controls.defaultRootPath.value === "Please Select") {
if (form.controls.defaultRootPath) {
if (form.controls.defaultRootPath.value === "Please Select") {
this.notificationService.error("Please check your entered values");
}
}

View file

@ -19,7 +19,7 @@ export class UpdateDetailsComponent implements OnInit {
this.identityService.getUser().subscribe(x => {
const localUser = x as IUpdateLocalUser;
this.form = this.fb.group({
id:[localUser.id],
id: [localUser.id],
username: [localUser.userName],
emailAddress: [localUser.emailAddress, [Validators.email]],
confirmNewPassword: [localUser.confirmNewPassword],

View file

@ -1,6 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings,IUser } from "../interfaces";
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces";
import { IdentityService, NotificationService, SettingsService } from "../services";
@Component({
@ -21,9 +21,9 @@ export class UserManagementComponent implements OnInit {
public bulkMovieLimit?: number;
public bulkEpisodeLimit?: number;
constructor(private readonly identityService: IdentityService,
private readonly settingsService: SettingsService,
private readonly notificationService: NotificationService) { }
constructor(private identityService: IdentityService,
private settingsService: SettingsService,
private notificationService: NotificationService) { }
public ngOnInit() {
this.users = [];
@ -37,7 +37,7 @@ export class UserManagementComponent implements OnInit {
}
public welcomeEmail(user: IUser) {
if(!user.emailAddress) {
if (!user.emailAddress) {
this.notificationService.error("The user needs an email address.");
return;
}
@ -45,7 +45,7 @@ export class UserManagementComponent implements OnInit {
this.notificationService.error("Email Notifications are not setup, cannot send welcome email");
return;
}
if(!this.emailSettings.notificationTemplates.some(x => {
if (!this.emailSettings.notificationTemplates.some(x => {
return x.enabled && x.notificationType === 8;
})) {
this.notificationService.error("The Welcome Email template is not enabled in the Email Setings");
@ -74,20 +74,20 @@ export class UserManagementComponent implements OnInit {
});
this.users.forEach(x => {
if(!x.checked) {
if (!x.checked) {
return;
}
if(anyRoles) {
if (anyRoles) {
x.claims = this.availableClaims;
}
if(this.bulkEpisodeLimit) {
if (this.bulkEpisodeLimit) {
x.episodeRequestLimit = this.bulkEpisodeLimit;
}
if(this.bulkMovieLimit) {
if (this.bulkMovieLimit) {
x.movieRequestLimit = this.bulkMovieLimit;
}
this.identityService.updateUser(x).subscribe(y => {
if(!y.successful) {
if (!y.successful) {
this.notificationService.error(`Could not update user ${x.userName}. Reason ${y.errors[0]}`);
}
});

View file

@ -11,7 +11,7 @@ import { IEmbySettings } from "../../interfaces";
})
export class EmbyComponent implements OnInit {
private embySettings: IEmbySettings;
public embySettings: IEmbySettings;
constructor(private embyService: EmbyService,
private router: Router,
@ -21,7 +21,7 @@ export class EmbyComponent implements OnInit {
public ngOnInit() {
this.embySettings = {
servers: [],
id:0,
id: 0,
enable: true,
};
this.embySettings.servers.push({

View file

@ -50,9 +50,9 @@ export class PlexComponent implements OnInit {
}
public oauth() {
this.plexTv.GetPin(this.clientId, "Ombi").subscribe(pin => {
this.plexService.oAuth({wizard: true, pin}).subscribe(x => {
if(x.url) {
this.plexTv.GetPin(this.clientId, "Ombi").subscribe((pin: any) => {
this.plexService.oAuth({ wizard: true, pin }).subscribe(x => {
if (x.url) {
window.location.href = x.url;
}
});

View file

@ -24,7 +24,7 @@ export class PlexOAuthComponent implements OnInit {
public ngOnInit(): void {
this.plexOauth.oAuth(this.pinId).subscribe(x => {
if(!x.accessToken) {
if (!x.accessToken) {
return;
// RETURN
}
@ -50,5 +50,4 @@ export class PlexOAuthComponent implements OnInit {
});
}
}

View file

@ -1,2 +0,0 @@
import "rxjs/add/operator/catch";
import "rxjs/add/operator/map";

View file

@ -1,5 +1,6 @@
import * as Pace from "pace-progress";
// Main
import * as Pace from "pace-progress";
Pace.start();
import "bootstrap/dist/js/bootstrap";
@ -11,8 +12,6 @@ import "./polyfills";
import "hammerjs";
import "./imports";
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";
@ -29,7 +28,9 @@ if (module.hot) {
oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
oldRootElem.parentNode.removeChild(oldRootElem);
}
if (modulePromise) {
modulePromise.then(appModule => appModule.destroy());
}
});
} else {
enableProdMode();

View file

@ -9,6 +9,7 @@ declare var module: any;
if (module.hot) {
Error.stackTraceLimit = Infinity;
// tslint:disable:no-var-requires
// tslint:disable-next-line
require("zone.js/dist/long-stack-trace-zone");
}

View file

@ -24,3 +24,7 @@ $bg-colour-disabled: #252424;
background: $primary-colour !important;
color: white;
}*/
.label {
margin: 3px;
}

View file

@ -883,11 +883,6 @@ textarea {
display: none;
}
.ui-treetable-toggler.fa.fa-fw.ui-clickable.fa-caret-right,
.ui-treetable-toggler.fa.fa-fw.ui-clickable.fa-caret-down {
display: none;
}
.ui-state-highlight {
background: $primary-colour;
}
@ -900,15 +895,6 @@ textarea {
border: 1px solid $form-color-lighter;
}
.ui-treetable tfoot td, .ui-treetable th {
text-align: left;
}
.ui-treetable tbody td {
white-space: inherit;
overflow: visible;
}
table a:not(.btn) {
text-decoration: none;
}

View file

@ -153,18 +153,6 @@ namespace Ombi.Controllers
return await MovieRequestEngine.DenyMovieById(model.Id);
}
/// <summary>
/// Gets the tv requests.
/// </summary>
/// <param name="count">The count of items you want to return.</param>
/// <param name="position">The position.</param>
/// <returns></returns>
[HttpGet("tv/{count:int}/{position:int}/tree")]
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetTvRequestsTree(int count, int position)
{
return await TvRequestEngine.GetRequestsTreeNode(count, position);
}
/// <summary>
/// Gets the total amount of TV requests.
/// </summary>
@ -267,17 +255,6 @@ namespace Ombi.Controllers
return await TvRequestEngine.SearchTvRequest(searchTerm);
}
/// <summary>
/// Searches for a specific tv request
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <returns></returns>
[HttpGet("tv/search/{searchTerm}/tree")]
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvTree(string searchTerm)
{
return await TvRequestEngine.SearchTvRequestTree(searchTerm);
}
/// <summary>
/// Deletes the a specific tv request
/// </summary>

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
@ -127,31 +126,6 @@ namespace Ombi.Controllers
return await TvEngine.Search(searchTerm);
}
/// <summary>
/// Searches for a Tv Show.
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <remarks>We use TvMaze as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/{searchTerm}/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTvTreeNode(string searchTerm)
{
return await TvEngine.SearchTreeNode(searchTerm);
}
/// <summary>
/// Gets extra show information.
/// </summary>
/// <param name="tvdbId">The TVDB identifier.</param>
/// <remarks>We use TvMaze as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/info/{tvdbId}/tree")]
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInfoTreeNode(int tvdbId)
{
if (tvdbId == 0) return new TreeNode<SearchTvShowViewModel>();
return await TvEngine.GetShowInformationTreeNode(tvdbId);
}
/// <summary>
/// Gets extra show information.
/// </summary>
@ -164,17 +138,6 @@ namespace Ombi.Controllers
return await TvEngine.GetShowInformation(tvdbId);
}
/// <summary>
/// Returns Popular Tv Shows
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/popular/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTvTree()
{
return await TvEngine.PopularTree();
}
/// <summary>
/// Returns Popular Tv Shows
/// </summary>
@ -186,18 +149,6 @@ namespace Ombi.Controllers
return await TvEngine.Popular();
}
/// <summary>
/// Returns most Anticiplateds tv shows.
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/anticipated/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTvTree()
{
return await TvEngine.AnticipatedTree();
}
/// <summary>
/// Returns most Anticiplateds tv shows.
/// </summary>
@ -209,16 +160,6 @@ namespace Ombi.Controllers
return await TvEngine.Anticipated();
}
/// <summary>
/// Returns Most watched shows.
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/mostwatched/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchedTree()
{
return await TvEngine.MostWatchesTree();
}
/// <summary>
/// Returns Most watched shows.
@ -231,17 +172,6 @@ namespace Ombi.Controllers
return await TvEngine.MostWatches();
}
/// <summary>
/// Returns trending shows
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/trending/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
return await TvEngine.TrendingTree();
}
/// <summary>
/// Returns trending shows
/// </summary>

View file

@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
@ -9,6 +8,7 @@
<FileVersion>$(SemVer)</FileVersion>
<Version>$(FullVer)</Version>
<PackageVersion></PackageVersion>
<TypeScriptToolsVersion>2.8</TypeScriptToolsVersion>
</PropertyGroup>
<PropertyGroup>
<ServerGarbageCollection>false</ServerGarbageCollection>
@ -24,10 +24,6 @@
<DefineConstants>TRACE;RELEASE;NETCOREAPP2_0</DefineConstants>
</PropertyGroup>
<Target Name="NpmCommandsDebug" Condition="'$(Configuration)'=='Debug'" AfterTargets="Build">
<Exec Command="npm run-script vendor" />
</Target>
<ItemGroup>
<!-- Files not to show in IDE -->
<Compile Remove="Logs\**" />
@ -46,7 +42,17 @@
<None Remove="Styles\**" />
<None Remove="wwwroot\dist\**" />
</ItemGroup>
<Target Name="NpmCommandsDebug" Condition="'$(Configuration)'=='Debug'" AfterTargets="Build">
<Exec Command="npm run vendor" />
</Target>
<!-- Disabled as run by CI once for many different runtime builds
<Target Name="NpmCommandsRelease" Condition="'$(Configuration)'=='Release'" AfterTargets="Build">
<Exec Command="npm run publish" />
</Target>
-->
<Target Name="NpmCommandsClean" AfterTargets="Clean">
<Exec Command="npm run clean" />
</Target>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
<ItemGroup>
<DistFiles Include="wwwroot\dist\**" />
@ -56,8 +62,6 @@
</ResolvedFileToPublish>
</ItemGroup>
</Target>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
@ -94,18 +98,4 @@
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
<ProjectReference Include="..\Ombi.Updater\Ombi.Updater.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\loading.css" />
<None Include="wwwroot\translations\*.json" />
</ItemGroup>
<ItemGroup>
<None Remove="ClientApp\app\animations\fadeinout.ts" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="ClientApp\app\animations\fadeinout.ts" />
</ItemGroup>
</Project>

View file

@ -156,7 +156,12 @@ namespace Ombi
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true,
ConfigFile = "webpack.dev.js"
ConfigFile = "webpack.config.ts",
//EnvParam = new
//{
// aot = true // can't use AOT with HMR currently https://github.com/angular/angular-cli/issues/6347
//}
});
}

View file

@ -71,8 +71,8 @@
<meta name="smartbanner:button-url-apple" content="https://itunes.apple.com/us/genre/ios/id36?mt=8">
<meta name="smartbanner:button-url-google" content="https://play.google.com/store">
<meta name="smartbanner:enabled-platforms" content="android,ios">e.com/store/apps/details?id=com.tidusjar.Ombi">
<meta name="smartbanner:enabled-platforms" content="android,ios">
<!-- End SmartBanner configuration -->*@
<meta name="smartbanner:enabled-platforms" content="android,ios">
<!-- End SmartBanner configuration -->*@
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="Ombi, media request tool">
@ -98,8 +98,10 @@
<link rel="stylesheet" href="~/loading.css" asp-append-version="true" />
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
<environment include="Development">
<script src="~/dist/vendor.js" asp-append-version="true" defer></script>
<script src="~/dist/main.js" asp-append-version="true" defer></script>
</environment>
<script src="~/dist/app.js" asp-append-version="true" defer></script>
</head>
<body>
@{

View file

@ -1,21 +1,25 @@
'use strict';
"use strict";
const gulp = require('gulp');
const run = require('gulp-run');
const runSequence = require('run-sequence');
const del = require('del');
const path = require('path');
const fs = require('fs');
const gulp = require("gulp");
const run = require("gulp-run");
const runSequence = require("run-sequence");
const del = require("del");
const path = require("path");
const fs = require("fs");
const outputDir = './wwwroot/dist';
const outputDir = "./wwwroot/dist";
global.aot = true;
function getEnvOptions() {
var options = [];
const options = [];
if (global.prod) {
options.push('--env.prod');
options.push("--env.prod");
}
if (global.analyse) {
options.push('--env.analyse');
options.push("--env.analyse");
}
if (global.aot) {
options.push("--env.aot");
}
if (options.length > 0) {
return " " + options.join(" ");
@ -24,12 +28,12 @@ function getEnvOptions() {
}
}
function webpack(vendor) {
return run(`webpack --config webpack.config${vendor ? '.vendor' : ''}.ts${getEnvOptions()}`).exec();
function webpack(type) {
// 'webpack' instead of direct path can cause https://github.com/angular/angular-cli/issues/6417
return run(`node node_modules\\webpack\\bin\\webpack.js --config webpack.config${type ? `.${type}` : ""}.ts${getEnvOptions()}`).exec();
}
gulp.task('vendor', () => {
gulp.task("vendor", () => {
let build = false;
const vendorPath = path.join(outputDir, "vendor.js");
const vendorExists = fs.existsSync(vendorPath);
@ -37,44 +41,47 @@ gulp.task('vendor', () => {
const vendorStat = fs.statSync(vendorPath);
const packageStat = fs.statSync("package.json");
const vendorConfigStat = fs.statSync("webpack.config.vendor.ts");
const commonConfigStat = fs.statSync("webpack.config.common.ts");
if (packageStat.mtime > vendorStat.mtime) {
build = true;
}
if (vendorConfigStat.mtime > vendorStat.mtime) {
build = true;
}
if (commonConfigStat.mtime > vendorStat.mtime) {
build = true;
}
} else {
build = true;
}
if (build) {
return webpack(true);
return webpack("vendor");
}
});
gulp.task('vendor_force', () => {
return webpack(true);
gulp.task("vendor_force", () => {
return webpack("vendor");
})
gulp.task('main', () => {
gulp.task("main", () => {
return webpack()
});
gulp.task('prod_var', () => {
gulp.task("prod_var", () => {
global.prod = true;
})
gulp.task('analyse_var', () => {
gulp.task("analyse_var", () => {
global.analyse = true;
})
gulp.task('clean', () => {
gulp.task("clean", () => {
del.sync(outputDir, { force: true });
});
gulp.task('lint', () => run("npm run lint").exec());
gulp.task('build', callback => runSequence('vendor', 'main', callback));
gulp.task('analyse', callback => runSequence('analyse_var', 'build'));
gulp.task('full', callback => runSequence('clean', 'build'));
gulp.task('publish', callback => runSequence('prod_var', 'build'));
gulp.task("lint", () => run("npm run lint").exec());
gulp.task("lint_fix", () => run("npm run lint -- --fix").exec());
gulp.task("build", callback => runSequence("vendor", "main", callback));
gulp.task("analyse", callback => runSequence("analyse_var", "clean", "build", callback));
gulp.task("full", callback => runSequence("clean", "build", callback));
gulp.task("publish", callback => runSequence("prod_var", "full", callback));

13399
src/Ombi/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,94 +1,93 @@
{
"name": "ombi",
"version": "2.6.0",
"private": true,
"version": "1.0.0",
"scripts": {
"vendor": "gulp vendor",
"main": "gulp main",
"lint": "tslint -p .",
"publish": "gulp publish",
"lint": "tslint ClientApp/**/*.ts",
"restore": "dotnet restore && npm install"
"restore": "dotnet restore && yarn install",
"clean": "gulp clean"
},
"dependencies": {
"@angular/animations": "^5.2.5",
"@angular/cdk": "^5.2.1",
"@angular/common": "^5.2.5",
"@angular/compiler": "^5.2.5",
"@angular/core": "^5.2.5",
"@angular/forms": "^5.2.5",
"@angular/http": "^5.2.5",
"@angular/material": "^5.2.1",
"@angular/platform-browser": "^5.2.5",
"@angular/platform-browser-dynamic": "^5.2.5",
"@angular/platform-server": "^5.2.5",
"@angular/router": "^5.2.5",
"@auth0/angular-jwt": "1.0.0-beta.9",
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
"@ngu/carousel": "^1.4.8",
"@ngx-translate/core": "^8.0.0",
"@ngx-translate/http-loader": "^2.0.1",
"@types/core-js": "^0.9.46",
"@types/extract-text-webpack-plugin": "^3.0.1",
"@types/intro.js": "^2.4.3",
"@types/node": "^8.9.4",
"@types/webpack": "^3.8.7",
"angular-router-loader": "^0.8.2",
"angular2-moment": "^1.8.0",
"@angular/animations": "^6.0.7",
"@angular/cdk": "^6.3.1",
"@angular/common": "^6.0.7",
"@angular/compiler": "^6.0.7",
"@angular/compiler-cli": "^6.0.7",
"@angular/core": "^6.0.7",
"@angular/forms": "^6.0.7",
"@angular/http": "^6.0.7",
"@angular/material": "^6.3.1",
"@angular/platform-browser": "^6.0.7",
"@angular/platform-browser-dynamic": "^6.0.7",
"@angular/platform-server": "^6.0.7",
"@angular/router": "^6.0.7",
"@auth0/angular-jwt": "^2.0.0",
"@ng-bootstrap/ng-bootstrap": "^2.2.0",
"@ngtools/webpack": "^6.1.0-beta.2",
"@ngu/carousel": "^1.4.9-beta-2",
"@ngx-translate/core": "^10.0.2",
"@ngx-translate/http-loader": "^3.0.1",
"@types/core-js": "^2.5.0",
"@types/mini-css-extract-plugin": "^0.2.0",
"@types/node": "^10.5.1",
"@types/webpack": "^4.4.4",
"@types/webpack-bundle-analyzer": "^2.9.2",
"@types/webpack-merge": "^4.1.3",
"angular-router-loader": "^0.8.5",
"angular2-template-loader": "^0.6.2",
"aspnet-webpack": "^2.0.3",
"awesome-typescript-loader": "^3.4.1",
"aspnet-webpack": "^3.0.0",
"awesome-typescript-loader": "^5.2.0",
"bootstrap": "3.3.7",
"bootswatch": "3.3.7",
"core-js": "^2.5.3",
"css": "^2.2.1",
"css-loader": "^0.28.9",
"copy-webpack-plugin": "^4.5.2",
"core-js": "^2.5.7",
"css": "^2.2.3",
"css-loader": "^0.28.11",
"del": "^3.0.0",
"event-source-polyfill": "^0.0.11",
"expose-loader": "^0.7.4",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"event-source-polyfill": "^0.0.12",
"expose-loader": "^0.7.5",
"file-loader": "^1.1.11",
"font-awesome": "^4.7.0",
"gulp": "^3.9.1",
"gulp-run": "^1.7.1",
"hammerjs": "^2.0.8",
"html-loader": "0.5.1",
"html-loader": "^0.5.5",
"jquery": "^3.3.1",
"mini-css-extract-plugin": "^0.4.1",
"moment": "^2.22.2",
"ng2-cookies": "^1.0.12",
"ngx-clipboard": "8.1.1",
"ngx-infinite-scroll": "^0.6.1",
"ngx-clipboard": "^11.1.1",
"ngx-infinite-scroll": "^6.0.1",
"ngx-moment": "^3.0.1",
"ngx-order-pipe": "^2.0.1",
"node-sass": "^4.7.2",
"npm": "^5.6.0",
"node-sass": "^4.9.0",
"pace-progress": "^1.0.2",
"primeng": "5.0.2",
"reflect-metadata": "0.1.10",
"primeng": "^6.0.0",
"raw-loader": "^0.5.1",
"reflect-metadata": "^0.1.12",
"run-sequence": "^2.2.1",
"rxjs": "5.5.2",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.1",
"rxjs": "^6.2.1",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"to-string-loader": "^1.1.5",
"ts-node": "^3.3.0",
"tslint": "^5.9.1",
"tslint-language-service": "^0.9.8",
"typescript": "^2.7.1",
"uglify-es": "^3.3.10",
"uglifyjs-webpack-plugin": "^1.1.8",
"url-loader": "^0.6.2",
"webpack": "^3.11.0",
"webpack-bundle-analyzer": "^2.10.0",
"webpack-hot-middleware": "^2.21.0",
"zone.js": "^0.8.20"
"ts-node": "^7.0.0",
"tslint": "^5.10.0",
"tslint-language-service": "^0.9.9",
"typescript": "2.7.2",
"uglify-es": "^3.3.9",
"uglifyjs-webpack-plugin": "^1.2.7",
"url-loader": "^1.0.1",
"webpack": "^4.14.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8",
"webpack-dev-middleware": "^3.1.3",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@types/chai": "4.0.4",
"@types/jasmine": "2.6.2",
"chai": "4.1.2",
"jasmine-core": "2.8.0",
"karma": "1.7.1",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-jasmine": "1.1.0",
"karma-webpack": "2.0.5"
"resolutions": {
"@types/tapable": "1.0.0"
}
}

View file

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es5",
"lib": [
"es6",
"es2017",
"dom"
],
"moduleResolution": "node",
@ -27,11 +27,15 @@
}
]
},
"exclude": [
"bin",
"node_modules"
"include": [
"ClientApp/**/*",
"typings/**/*",
"webpack.config.*"
],
"angularCompilerOptions": {
"preserveWhitespaces": false
}
"preserveWhitespaces": false,
"genDir": "./ClientApp/app/ngfactory",
"entryModule": "ClientApp/app/app.module#AppModule"
},
"compileOnSave": false
}

View file

@ -8,7 +8,7 @@
],
"max-line-length": [
true,
200
250
],
"arrow-parens": false,
"radix": false,
@ -16,22 +16,33 @@
"indent": [
false
],
"whitespace": [
false
],
"no-unused-expression": [
true,
"allow-new"
],
"no-trailing-whitespace": [
false
],
"max-classes-per-file": [
false
],
"no-shadowed-variable": false,
"comment-format": [
false
],
"no-namespace": [
false
],
"no-internal-module": [
false
],
"whitespace": [
false,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-trailing-whitespace":[
false
]
}
}

4
src/Ombi/typings/globals.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
// Globals
declare module "pace-progress";
declare var __webpack_public_path__: any;

View file

@ -1,12 +0,0 @@
// Globals
declare var module: any;
declare var require: any;
declare var localStorage: any;
declare var introJs: any;
declare var __webpack_public_path__: any;
declare module "pace-progress";
declare module "webpack-bundle-analyzer";
declare module "uglifyjs-webpack-plugin";

View file

@ -1 +1 @@
/// <reference path="globals/globals.d.ts" />
/// <reference path="globals.d.ts" />

View file

@ -0,0 +1,83 @@
import { AngularCompilerPlugin } from "@ngtools/webpack";
import * as MiniCssExtractPlugin from "mini-css-extract-plugin";
import * as path from "path";
import { Configuration, ContextReplacementPlugin, ProvidePlugin } from "webpack";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
export const outputDir = "./wwwroot/dist";
export function isProd(env: any) {
return env && env.prod as boolean;
}
export function isAOT(env: any) {
return env && env.aot as boolean;
}
export const WebpackCommonConfig = (env: any, type: string) => {
const prod = isProd(env);
const aot = isAOT(env);
const vendor = type === "vendor";
console.log(`${prod ? "Production" : "Dev"} ${type} build`);
console.log(`Output directory: ${outputDir}`);
console.log(`${aot ? "Using" : "Not using"} AOT compiler`);
const analyse = env && env.analyse as boolean;
if (analyse) { console.log("Analysing build"); }
const cssLoader = prod ? "css-loader?minimize" : "css-loader";
const bundleConfig: Configuration = {
mode: prod ? "production" : "development",
resolve: {
extensions: [".ts", ".js"],
alias: {
pace: "pace-progress",
},
},
output: {
path: path.resolve(outputDir),
filename: "[name].js",
chunkFilename: "[id].chunk.js",
publicPath: "/dist/",
},
module: {
rules: [
{ test: /\.ts$/, loader: aot ? "@ngtools/webpack" : ["awesome-typescript-loader?silent=true", "angular2-template-loader", "angular-router-loader"] },
{ test: /\.html$/, use: "html-loader?minimize=false" },
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, cssLoader] },
{ test: /\.scss$/, exclude: /ClientApp/, use: [MiniCssExtractPlugin.loader, cssLoader, "sass-loader"] },
{ test: /\.scss$/, include: /ClientApp(\\|\/)app/, use: ["to-string-loader", cssLoader, "sass-loader"] },
{ test: /\.scss$/, include: /ClientApp(\\|\/)styles/, use: ["style-loader", cssLoader, "sass-loader"] },
{ test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf|svg)(\?|$)/, use: "url-loader?limit=8192" },
{ test: /[\/\\]@angular[\/\\].+\.js$/, parser: { system: true } }, // ignore System.import warnings https://github.com/angular/angular/issues/21560
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers
].concat(aot && !vendor ? [
new AngularCompilerPlugin({
mainPath: "./ClientApp/main.ts",
tsConfigPath: "./tsconfig.json",
skipCodeGeneration: false,
compilerOptions: {
noEmit: false,
},
}),
] : [
// AOT chunk splitting does not work while this is active but doesn't seem to be needed under AOT anyway https://github.com/angular/angular-cli/issues/4431
new ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898
]).concat(analyse ? [
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: `${type}.html`,
openAnalyzer: false,
}),
] : []),
node: {
fs: "empty",
},
};
return bundleConfig;
};

View file

@ -1,59 +1,26 @@
import { CheckerPlugin } from "awesome-typescript-loader";
import * as path from "path";
import * as UglifyJSPlugin from "uglifyjs-webpack-plugin";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import { Configuration, DllReferencePlugin } from "webpack";
import * as webpackMerge from "webpack-merge";
import * as webpack from "webpack";
import { isAOT, isProd, outputDir, WebpackCommonConfig } from "./webpack.config.common";
module.exports = (env: any) => {
// const prod = env && env.prod as boolean;
const prod = true;
console.log(prod ? "Production" : "Dev" + " main build");
const analyse = env && env.analyse as boolean;
if (analyse) { console.log("Analysing build"); }
const cssLoader = prod ? "css-loader?-url&minimize" : "css-loader?-url";
const outputDir = "./wwwroot/dist";
const bundleConfig: webpack.Configuration = {
entry: { main: "./ClientApp/main.ts" },
stats: { modules: false },
context: __dirname,
resolve: { extensions: [".ts", ".js"] },
const prod = isProd(env);
const aot = isAOT(env);
if (!prod && aot) { console.warn("Vendor dll bundle will not be used as AOT is enabled"); }
const bundleConfig: Configuration = webpackMerge(WebpackCommonConfig(env, "main"), {
entry: {
app: "./ClientApp/main.ts",
},
devtool: prod ? "source-map" : "eval-source-map",
output: {
filename: "[name].js",
chunkFilename: "[id].[chunkhash].js",
publicPath: "/dist/",
path: path.join(__dirname, outputDir),
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: ["awesome-typescript-loader?silent=true", "angular2-template-loader", "angular-router-loader"] },
{ test: /\.html$/, use: "html-loader?minimize=false" },
{ test: /\.css$/, use: ["to-string-loader", cssLoader] },
{ test: /\.scss$/, include: /ClientApp(\\|\/)app/, use: ["to-string-loader", cssLoader, "sass-loader"] },
{ test: /\.scss$/, include: /ClientApp(\\|\/)styles/, use: ["style-loader", cssLoader, "sass-loader"] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: "url-loader?limit=25000" },
],
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
plugins: prod || aot ? [] : [
// AOT chunk splitting does not work while this is active https://github.com/angular/angular-cli/issues/4565
new DllReferencePlugin({
context: __dirname,
manifest: require(path.join(__dirname, outputDir, "vendor-manifest.json")),
}),
].concat(prod ? [
// Plugins that apply in production builds only
new UglifyJSPlugin({ sourceMap: true }),
] : [
// Plugins that apply in development builds only
]).concat(analyse ? [
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "main.html",
openAnalyzer: false,
}),
] : []),
};
],
});
return bundleConfig;
};

View file

@ -1,41 +1,25 @@
import * as ExtractTextPlugin from "extract-text-webpack-plugin";
import * as path from "path";
import * as UglifyJSPlugin from "uglifyjs-webpack-plugin";
import * as webpack from "webpack";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import * as webpackMerge from "webpack-merge";
import { isProd, outputDir, WebpackCommonConfig } from "./webpack.config.common";
module.exports = (env: any) => {
const extractCSS = new ExtractTextPlugin("vendor.css");
const prod = env && env.prod as boolean;
console.log(prod ? "Production" : "Dev" + " vendor build");
const analyse = env && env.analyse as boolean;
if (analyse) { console.log("Analysing build"); }
const outputDir = "./wwwroot/dist";
const bundleConfig = {
stats: { modules: false },
resolve: {
extensions: [".js"],
alias: {
pace: "pace-progress",
},
},
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg|gif)(\?|$)/, use: "url-loader?limit=100000" },
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: prod ? "css-loader?minimize" : "css-loader" }) },
{ test: /\.scss(\?|$)/, use: extractCSS.extract({ use: [prod ? "css-loader?minimize" : "css-loader", "sass-loader"] }) },
],
const prod = isProd(env);
const bundleConfig = webpackMerge(WebpackCommonConfig(env, "vendor"), {
output: {
library: "[name]_[hash]",
},
entry: {
vendor: [
vendor: (<string[]>[ // add any vendor styles here e.g. bootstrap/dist/css/bootstrap.min.css
"pace-progress/themes/orange/pace-theme-flash.css",
"primeng/resources/primeng.min.css",
"@angular/material/prebuilt-themes/deeppurple-amber.css",
"font-awesome/scss/font-awesome.scss",
"bootswatch/superhero/bootstrap.min.css",
]).concat(prod ? [] : [ // used to speed up dev launch time
"@angular/animations",
"@angular/common",
"@angular/common/http",
"@angular/compiler",
"@angular/core",
"@angular/forms",
@ -67,41 +51,14 @@ module.exports = (env: any) => {
"@ngx-translate/core",
"@ngx-translate/http-loader",
"ngx-order-pipe",
//"smartbanner.js/dist/smartbanner.js",
//"smartbanner.js/dist/smartbanner.css",
],
]),
},
output: {
publicPath: "/dist/",
filename: "[name].js",
library: "[name]_[hash]",
path: path.join(__dirname, outputDir),
},
node: {
fs: "empty",
},
plugins: [
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, "./client")), // Workaround for https://github.com/angular/angular/issues/20357
new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580
new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898
extractCSS,
plugins: prod ? [] : [
new webpack.DllPlugin({
path: path.join(__dirname, outputDir, "[name]-manifest.json"),
name: "[name]_[hash]",
}),
].concat(prod ? [
// Plugins that apply in production builds only
new UglifyJSPlugin(),
] : [
// Plugins that apply in development builds only
]).concat(analyse ? [
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "vendor.html",
openAnalyzer: false,
}),
] : []),
};
],
});
return bundleConfig;
};

View file

@ -1,4 +0,0 @@
// https://github.com/aspnet/JavaScriptServices/issues/1046
require('ts-node/register')
module.exports = require("./webpack.config.ts");

View file

@ -1,171 +1,171 @@
{
"Login": {
"SignInButton": "Sign in",
"UsernamePlaceholder": "Username",
"PasswordPlaceholder": "Password",
"RememberMe": "Remember Me",
"ForgottenPassword": "Forgot your password?",
"SignInButton": "Zaloguj się",
"UsernamePlaceholder": "Nazwa użytkownika",
"PasswordPlaceholder": "Hasło",
"RememberMe": "Zapamiętaj mnie",
"ForgottenPassword": "Nie pamiętasz hasła?",
"Errors": {
"IncorrectCredentials": "Incorrect username or password"
"IncorrectCredentials": "Nieprawidłowa nazwa użytkownika lub hasło"
}
},
"Common": {
"ContinueButton": "Continue",
"Available": "Available",
"NotAvailable": "Not Available",
"ProcessingRequest": "Processing Request",
"PendingApproval": "Pending Approval",
"RequestDenied": "Request Denied",
"NotRequested": "Not Requested",
"Requested": "Requested",
"Request": "Request",
"Denied": "Denied",
"Approve": "Approve",
"PartlyAvailable": "Partly Available",
"ContinueButton": "Dalej",
"Available": "Dostępny",
"NotAvailable": "Niedostępny",
"ProcessingRequest": "Przetwarzanie zgłoszenia",
"PendingApproval": "Oczekujące na zatwierdzenie",
"RequestDenied": "Zgłoszenie odrzucone",
"NotRequested": "Nie zgłoszone",
"Requested": "Zgłoszone",
"Request": "Zgłoszenie",
"Denied": "Odrzucone",
"Approve": "Zatwierdź",
"PartlyAvailable": "Dostępne częściowo",
"Errors": {
"Validation": "Please check your entered values"
"Validation": "Sprawdź wprowadzone dane"
}
},
"PasswordReset": {
"EmailAddressPlaceholder": "Email Address",
"ResetPasswordButton": "Reset Password"
"EmailAddressPlaceholder": "Adres e-mail",
"ResetPasswordButton": "Zresetuj hasło"
},
"LandingPage": {
"OnlineHeading": "Currently Online",
"OnlineParagraph": "The media server is currently online",
"PartiallyOnlineHeading": "Partially Online",
"PartiallyOnlineParagraph": "The media server is partially online.",
"MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.",
"SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.",
"OfflineHeading": "Currently Offline",
"OfflineParagraph": "The media server is currently offline.",
"CheckPageForUpdates": "Check this page for continuous site updates."
"OnlineHeading": "Dostępny",
"OnlineParagraph": "Serwer mediów jest dostępny",
"PartiallyOnlineHeading": "Dostępny częściowo",
"PartiallyOnlineParagraph": "Serwer mediów jest dostępny częściowo.",
"MultipleServersUnavailable": "{{serversUnavailable}} serwerów spośród {{totalServers}} jest niedostępnych.",
"SingleServerUnavailable": "Jeden serwer spośród {{totalServers}} jest niedostępny.",
"OfflineHeading": "Niedostępny",
"OfflineParagraph": "Serwer mediów jest niedostępny.",
"CheckPageForUpdates": "Tutaj znajdziesz aktualizacje dotyczące tej strony."
},
"NavigationBar": {
"Search": "Search",
"Requests": "Requests",
"UserManagement": "User Management",
"Issues": "Issues",
"Donate": "Donate!",
"DonateLibraryMaintainer": "Donate to Library Maintainer",
"DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi ;)",
"UpdateAvailableTooltip": "Update Available!",
"Settings": "Settings",
"Welcome": "Welcome {{username}}",
"UpdateDetails": "Update Details",
"Logout": "Logout",
"Search": "Szukaj",
"Requests": "Zgłoszenia",
"UserManagement": "Zarządzanie użytkownikami",
"Issues": "Problemy",
"Donate": "Wesprzyj!",
"DonateLibraryMaintainer": "Wesprzyj właściciela biblioteki",
"DonateTooltip": "W ten sposób przekonuję moją żonę by spędzać mój wolny czas rozwijając Ombi ;)",
"UpdateAvailableTooltip": "Dostępna aktualizacja!",
"Settings": "Ustawienia",
"Welcome": "Witaj {{username}}",
"UpdateDetails": "Podaj szczegóły",
"Logout": "Wyloguj",
"Language": {
"English": "English",
"French": "French",
"Spanish": "Spanish",
"German": "German",
"Italian": "Italian",
"Danish": "Danish",
"Dutch": "Dutch",
"Norwegian": "Norwegian",
"BrazillianPortuguese": "Brazillian Portuguese",
"Polish": "Polish"
"English": "Angielski",
"French": "Francuski",
"Spanish": "Hiszpański",
"German": "Niemiecki",
"Italian": "Włoski",
"Danish": "Duński",
"Dutch": "Holenderski",
"Norwegian": "Norweski",
"BrazillianPortuguese": "Brazylijski portugalski",
"Polish": "Polski"
},
"OpenMobileApp": "Open Mobile App",
"RecentlyAdded": "Recently Added"
"OpenMobileApp": "Otwórz aplikację mobilną",
"RecentlyAdded": "Ostatnio dodane"
},
"Search": {
"Title": "Search",
"Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!",
"MoviesTab": "Movies",
"TvTab": "TV Shows",
"Suggestions": "Suggestions",
"NoResults": "Sorry, we didn't find any results!",
"DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "View On Plex",
"ViewOnEmby": "View On Emby",
"RequestAdded": "Request for {{title}} has been added successfully",
"Similar": "Similar",
"Title": "Szukaj",
"Paragraph": "Chcesz obejrzeć coś, co nie jest obecnie dostępne? Żaden problem, po prostu wyszukaj poniżej i dodaj zgłoszenie!",
"MoviesTab": "Filmy",
"TvTab": "Seriale",
"Suggestions": "Sugestie",
"NoResults": "Niestety nic nie znaleziono!",
"DigitalDate": "Wydanie cyfrowe: {{date}}",
"TheatricalRelease": "Premiera kinowa: {{date}}",
"ViewOnPlex": "Obejrzyj w Plex",
"ViewOnEmby": "Obejrzyj na Emby",
"RequestAdded": "Zgłoszenie dla {{title}} zostało dodane",
"Similar": "Podobne",
"Movies": {
"PopularMovies": "Popular Movies",
"UpcomingMovies": "Upcoming Movies",
"TopRatedMovies": "Top Rated Movies",
"NowPlayingMovies": "Now Playing Movies",
"HomePage": "Home Page",
"Trailer": "Trailer"
"PopularMovies": "Popularne filmy",
"UpcomingMovies": "Wkrótce w kinach",
"TopRatedMovies": "Najwyżej oceniane filmy",
"NowPlayingMovies": "W kinach",
"HomePage": "Strona główna",
"Trailer": "Zwiastun"
},
"TvShows": {
"Popular": "Popular",
"Trending": "Trending",
"MostWatched": "Most Watched",
"MostAnticipated": "Most Anticipated",
"Results": "Results",
"AirDate": "Air Date:",
"AllSeasons": "All Seasons",
"FirstSeason": "First Season",
"LatestSeason": "Latest Season",
"Select": "Select ...",
"SubmitRequest": "Submit Request",
"Season": "Season: {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}"
"Popular": "Popularne",
"Trending": "Zyskujące popularność",
"MostWatched": "Najczęściej oglądane",
"MostAnticipated": "Najbardziej oczekiwane",
"Results": "Wyniki",
"AirDate": "Data emisji:",
"AllSeasons": "Wszystkie sezony",
"FirstSeason": "Pierwszy sezon",
"LatestSeason": "Najnowszy sezon",
"Select": "Wybierz…",
"SubmitRequest": "Dodaj zgłoszenie",
"Season": "Sezon: {{seasonNumber}}",
"SelectAllInSeason": "Wybierz wszystkie w sezonie {{seasonNumber}}"
}
},
"Requests": {
"Title": "Requests",
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
"MoviesTab": "Movies",
"TvTab": "TV Shows",
"RequestedBy": "Requested By:",
"Status": "Status:",
"RequestStatus": "Request status:",
"Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}",
"TheatricalReleaseSort": "Theatrical Release",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Request Date:",
"QualityOverride": "Quality Override:",
"RootFolderOverride": "Root Folder Override:",
"ChangeRootFolder": "Root Folder",
"ChangeQualityProfile": "Quality Profile",
"MarkUnavailable": "Mark Unavailable",
"MarkAvailable": "Mark Available",
"Remove": "Remove",
"Deny": "Deny",
"Season": "Season:",
"GridTitle": "Title",
"AirDate": "AirDate",
"GridStatus": "Status",
"ReportIssue": "Report Issue",
"Filter": "Filter",
"Sort": "Sort",
"SeasonNumberHeading": "Season: {seasonNumber}",
"SortTitleAsc": "Title ▲",
"SortTitleDesc": "Title ▼",
"SortRequestDateAsc": "Request Date ▲",
"SortRequestDateDesc": "Request Date ▼",
"SortStatusAsc": "Status ▲",
"SortStatusDesc": "Status ▼"
"Title": "Zgłoszenia",
"Paragraph": "Poniżej są twoje i wszystkie inne zgłoszenia, a także ich status akceptacji i pobierania.",
"MoviesTab": "Filmy",
"TvTab": "Seriale",
"RequestedBy": "Zgłoszone przez:",
"Status": "Stan:",
"RequestStatus": "Stan zgłoszenia:",
"Denied": "Odrzucono:",
"TheatricalRelease": "Premiera kinowa: {{date}}",
"TheatricalReleaseSort": "Premiera kinowa",
"DigitalRelease": "Wydanie cyfrowe: {{date}}",
"RequestDate": "Data zgłoszenia:",
"QualityOverride": "Wymuszenie jakości:",
"RootFolderOverride": "Wymuszenie folderu głównego:",
"ChangeRootFolder": "Folder główny",
"ChangeQualityProfile": "Wybór jakości",
"MarkUnavailable": "Oznacz jako niedostępne",
"MarkAvailable": "Oznacz jako dostępne",
"Remove": "Usuń",
"Deny": "Odrzuć",
"Season": "Sezon:",
"GridTitle": "Tytuł",
"AirDate": "Data emisji",
"GridStatus": "Stan",
"ReportIssue": "Zgłoś problem",
"Filter": "Filtr",
"Sort": "Sortowanie",
"SeasonNumberHeading": "Sezon: {seasonNumber}",
"SortTitleAsc": "Tytuł ▲",
"SortTitleDesc": "Tytuł ▼",
"SortRequestDateAsc": "Data zgłoszenia ▲",
"SortRequestDateDesc": "Data zgłoszenia ▼",
"SortStatusAsc": "Stan ▲",
"SortStatusDesc": "Stan ▼"
},
"Issues": {
"Title": "Issues",
"PendingTitle": "Pending Issues",
"InProgressTitle": "In Progress Issues",
"ResolvedTitle": "Resolved Issues",
"ColumnTitle": "Title",
"Category": "Category",
"Status": "Status",
"Details": "Details",
"Description": "Description",
"NoComments": "No Comments!",
"MarkInProgress": "Mark In Progress",
"MarkResolved": "Mark Resolved",
"SendMessageButton": "Send",
"Subject": "Subject",
"Comments": "Comments",
"WriteMessagePlaceholder": "Write your message here...",
"ReportedBy": "Reported By"
"Title": "Problemy",
"PendingTitle": "Oczekujące problemy",
"InProgressTitle": "Problemy w trakcie",
"ResolvedTitle": "Problemy rozwiązane",
"ColumnTitle": "Tytuł",
"Category": "Kategoria",
"Status": "Stan",
"Details": "Szczegóły",
"Description": "Opis",
"NoComments": "Brak komentarzy!",
"MarkInProgress": "Oznacz jako \"w trakcie\"",
"MarkResolved": "Oznacz jako rozwiązane",
"SendMessageButton": "Wyślij",
"Subject": "Temat",
"Comments": "Komentarze",
"WriteMessagePlaceholder": "Tutaj wpisz swoją wiadomość…",
"ReportedBy": "Zgłoszone przez"
},
"Filter": {
"ClearFilter": "Clear Filter",
"FilterHeaderAvailability": "Availability",
"FilterHeaderRequestStatus": "Status",
"Approved": "Approved",
"PendingApproval": "Pending Approval"
"ClearFilter": "Wyczyść fltr",
"FilterHeaderAvailability": "Dostępność",
"FilterHeaderRequestStatus": "Stan",
"Approved": "Zatwierdzone",
"PendingApproval": "Oczekujące na zatwierdzenie"
}
}

View file

@ -1,171 +1,171 @@
{
"Login": {
"SignInButton": "Sign in",
"UsernamePlaceholder": "Username",
"PasswordPlaceholder": "Password",
"RememberMe": "Remember Me",
"ForgottenPassword": "Forgot your password?",
"SignInButton": "Registrar",
"UsernamePlaceholder": "Nome de usuário",
"PasswordPlaceholder": "Senha",
"RememberMe": "Lembre-se de mim",
"ForgottenPassword": "Esqueceu sua senha?",
"Errors": {
"IncorrectCredentials": "Incorrect username or password"
"IncorrectCredentials": "Nome de usuário ou senha incorretos"
}
},
"Common": {
"ContinueButton": "Continue",
"Available": "Available",
"NotAvailable": "Not Available",
"ProcessingRequest": "Processing Request",
"PendingApproval": "Pending Approval",
"RequestDenied": "Request Denied",
"NotRequested": "Not Requested",
"Requested": "Requested",
"Request": "Request",
"Denied": "Denied",
"Approve": "Approve",
"PartlyAvailable": "Partly Available",
"ContinueButton": "Continuar",
"Available": "Disponível",
"NotAvailable": "Inisponível",
"ProcessingRequest": "Processando Solicitação",
"PendingApproval": "Aprovação Pendente",
"RequestDenied": "Solicitação Negada",
"NotRequested": "Não Solicitado",
"Requested": "Solicitado",
"Request": "Solicitar",
"Denied": "Negado",
"Approve": "Aprovar",
"PartlyAvailable": "Parcialmente Disponível",
"Errors": {
"Validation": "Please check your entered values"
"Validation": "Por favor, verifique os dados inseridos"
}
},
"PasswordReset": {
"EmailAddressPlaceholder": "Email Address",
"ResetPasswordButton": "Reset Password"
"EmailAddressPlaceholder": "Endereço de e-mail",
"ResetPasswordButton": "Redefinir Senha"
},
"LandingPage": {
"OnlineHeading": "Currently Online",
"OnlineParagraph": "The media server is currently online",
"PartiallyOnlineHeading": "Partially Online",
"PartiallyOnlineParagraph": "The media server is partially online.",
"MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.",
"SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.",
"OfflineHeading": "Currently Offline",
"OfflineParagraph": "The media server is currently offline.",
"CheckPageForUpdates": "Check this page for continuous site updates."
"OnlineHeading": "Online Agora",
"OnlineParagraph": "O servidor de mídia está atualmente online",
"PartiallyOnlineHeading": "Parcialmente Online",
"PartiallyOnlineParagraph": "O servidor de mídia está parcialmente online.",
"MultipleServersUnavailable": "Existem {{serversUnavailable}} servidores offline em um total de {{totalServers}}.",
"SingleServerUnavailable": "Existe {{serversUnavailable}} servidor offline em um total de {{totalServers}}.",
"OfflineHeading": "Offline Agora",
"OfflineParagraph": "O servidor de mídia está atualmente offline.",
"CheckPageForUpdates": "Verifique esta página para atualizações contínuas do site."
},
"NavigationBar": {
"Search": "Search",
"Requests": "Requests",
"UserManagement": "User Management",
"Issues": "Issues",
"Donate": "Donate!",
"DonateLibraryMaintainer": "Donate to Library Maintainer",
"DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi ;)",
"UpdateAvailableTooltip": "Update Available!",
"Settings": "Settings",
"Welcome": "Welcome {{username}}",
"UpdateDetails": "Update Details",
"Logout": "Logout",
"Search": "Pesquisar",
"Requests": "Solicitações",
"UserManagement": "Gerenciador de Usuário",
"Issues": "Problemas",
"Donate": "Fazer uma doação!",
"DonateLibraryMaintainer": "Doar para o Dono da Biblioteca",
"DonateTooltip": "É assim que eu convenço a minha mulher a deixar-me passar o meu tempo livre desenvolvendo Ombi;)",
"UpdateAvailableTooltip": "Atualização Disponível!",
"Settings": "Configurações",
"Welcome": "Bem-vindo, {{username}}",
"UpdateDetails": "Detalhes da Atualização",
"Logout": "Sair",
"Language": {
"English": "English",
"French": "French",
"Spanish": "Spanish",
"German": "German",
"Italian": "Italian",
"Danish": "Danish",
"Dutch": "Dutch",
"Norwegian": "Norwegian",
"BrazillianPortuguese": "Brazillian Portuguese",
"Polish": "Polish"
"English": "Inglês",
"French": "Francês",
"Spanish": "Espanhol",
"German": "Alemão",
"Italian": "Italiano",
"Danish": "Dinamarquês",
"Dutch": "Holandês",
"Norwegian": "Norueguês",
"BrazillianPortuguese": "Português (Brasil)",
"Polish": "Polonês"
},
"OpenMobileApp": "Open Mobile App",
"RecentlyAdded": "Recently Added"
"OpenMobileApp": "Abrir aplicativo do celular",
"RecentlyAdded": "Recentemente adicionado"
},
"Search": {
"Title": "Search",
"Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!",
"MoviesTab": "Movies",
"TvTab": "TV Shows",
"Suggestions": "Suggestions",
"NoResults": "Sorry, we didn't find any results!",
"DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "View On Plex",
"ViewOnEmby": "View On Emby",
"RequestAdded": "Request for {{title}} has been added successfully",
"Similar": "Similar",
"Title": "Pesquisar",
"Paragraph": "Quer assistir a algo que não está disponível? Não há problema, basta procurar abaixo e solicitar!",
"MoviesTab": "Filmes",
"TvTab": "Séries",
"Suggestions": "Sugestões",
"NoResults": "Desculpe, não encontramos nenhum resultado!",
"DigitalDate": "Lançamento digital: {{date}}",
"TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
"ViewOnPlex": "Assistir no Plex",
"ViewOnEmby": "Assistir no Emby",
"RequestAdded": "Pedido de {{title}} foi adicionado com sucesso",
"Similar": "Semelhante",
"Movies": {
"PopularMovies": "Popular Movies",
"UpcomingMovies": "Upcoming Movies",
"TopRatedMovies": "Top Rated Movies",
"NowPlayingMovies": "Now Playing Movies",
"PopularMovies": "Filmes populares",
"UpcomingMovies": "Próximos filmes",
"TopRatedMovies": "Filmes mais votados",
"NowPlayingMovies": "Filmes em cartazes",
"HomePage": "Home Page",
"Trailer": "Trailer"
},
"TvShows": {
"Popular": "Popular",
"Trending": "Trending",
"MostWatched": "Most Watched",
"MostAnticipated": "Most Anticipated",
"Results": "Results",
"AirDate": "Air Date:",
"AllSeasons": "All Seasons",
"FirstSeason": "First Season",
"LatestSeason": "Latest Season",
"Select": "Select ...",
"SubmitRequest": "Submit Request",
"Season": "Season: {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}"
"Trending": "Mais populares",
"MostWatched": "Mais assistidos",
"MostAnticipated": "Mais esperados",
"Results": "Resultados",
"AirDate": "Data de exibição:",
"AllSeasons": "Todas as temporadas",
"FirstSeason": "Primeira temporada",
"LatestSeason": "Última temporada",
"Select": "Selecione...",
"SubmitRequest": "Enviar solicitação",
"Season": "Temporada: {{seasonNumber}}",
"SelectAllInSeason": "Selecione tudo na temporada {{seasonNumber}}"
}
},
"Requests": {
"Title": "Requests",
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
"MoviesTab": "Movies",
"TvTab": "TV Shows",
"RequestedBy": "Requested By:",
"Title": "Solicitações",
"Paragraph": "Abaixo, você pode ver o seu e todos os outros pedidos, bem como o seu download e status de aprovação.",
"MoviesTab": "Filmes",
"TvTab": "Séries",
"RequestedBy": "Solicitado por:",
"Status": "Status:",
"RequestStatus": "Request status:",
"Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}",
"TheatricalReleaseSort": "Theatrical Release",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Request Date:",
"QualityOverride": "Quality Override:",
"RootFolderOverride": "Root Folder Override:",
"ChangeRootFolder": "Root Folder",
"ChangeQualityProfile": "Quality Profile",
"MarkUnavailable": "Mark Unavailable",
"MarkAvailable": "Mark Available",
"Remove": "Remove",
"Deny": "Deny",
"Season": "Season:",
"GridTitle": "Title",
"AirDate": "AirDate",
"RequestStatus": "Status da solicitação:",
"Denied": " Negados:",
"TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
"TheatricalReleaseSort": "Lançamento nos Cinemas",
"DigitalRelease": "Lançamento digital: {{date}}",
"RequestDate": "Data da Solicitação:",
"QualityOverride": "Substituição de qualidade:",
"RootFolderOverride": "Substituição da pasta raiz:",
"ChangeRootFolder": "Pasta raiz",
"ChangeQualityProfile": "Perfil de qualidade",
"MarkUnavailable": "Marcar como Indisponível",
"MarkAvailable": "Marcar como Disponível",
"Remove": "Remover",
"Deny": "Negar",
"Season": "Temporada:",
"GridTitle": "Título",
"AirDate": "Data de exibição",
"GridStatus": "Status",
"ReportIssue": "Report Issue",
"Filter": "Filter",
"Sort": "Sort",
"SeasonNumberHeading": "Season: {seasonNumber}",
"SortTitleAsc": "Title ▲",
"SortTitleDesc": "Title ▼",
"SortRequestDateAsc": "Request Date ▲",
"SortRequestDateDesc": "Request Date ▼",
"ReportIssue": "Relatar Problema",
"Filter": "Filtro",
"Sort": "Ordenar por",
"SeasonNumberHeading": "Temporada: {seasonNumber}",
"SortTitleAsc": "Título ▲",
"SortTitleDesc": "Título ▼",
"SortRequestDateAsc": "Data da Solicitação",
"SortRequestDateDesc": "Data da Solicitação",
"SortStatusAsc": "Status ▲",
"SortStatusDesc": "Status ▼"
},
"Issues": {
"Title": "Issues",
"PendingTitle": "Pending Issues",
"InProgressTitle": "In Progress Issues",
"ResolvedTitle": "Resolved Issues",
"ColumnTitle": "Title",
"Category": "Category",
"Title": "Problemas",
"PendingTitle": "Problemas pendentes",
"InProgressTitle": "Resolvendo Problemas",
"ResolvedTitle": "Problemas Resolvidos",
"ColumnTitle": "Título",
"Category": "Categoria",
"Status": "Status",
"Details": "Details",
"Description": "Description",
"NoComments": "No Comments!",
"MarkInProgress": "Mark In Progress",
"MarkResolved": "Mark Resolved",
"SendMessageButton": "Send",
"Subject": "Subject",
"Comments": "Comments",
"WriteMessagePlaceholder": "Write your message here...",
"ReportedBy": "Reported By"
"Details": "Detalhes",
"Description": "Descrição",
"NoComments": "Sem Comentários!",
"MarkInProgress": "Marcar como em andamento",
"MarkResolved": "Marcar como resolvido",
"SendMessageButton": "Enviar",
"Subject": "Assunto",
"Comments": "Comentários",
"WriteMessagePlaceholder": "Escreva sua mensagem aqui...",
"ReportedBy": "Reportado por"
},
"Filter": {
"ClearFilter": "Clear Filter",
"FilterHeaderAvailability": "Availability",
"ClearFilter": "Limpar Filtro",
"FilterHeaderAvailability": "Disponibilidade",
"FilterHeaderRequestStatus": "Status",
"Approved": "Approved",
"PendingApproval": "Pending Approval"
"Approved": "Aprovado",
"PendingApproval": "Aprovação Pendente"
}
}

File diff suppressed because it is too large Load diff