mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 13:23:20 -07:00
parent
dd48149ad9
commit
d5e85ffdac
33 changed files with 8654 additions and 2231 deletions
47
.github/workflows/chromatic.yml
vendored
Normal file
47
.github/workflows/chromatic.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
name: 'Chromatic'
|
||||||
|
|
||||||
|
# Event for the workflow
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# List of jobs
|
||||||
|
jobs:
|
||||||
|
storybook-build:
|
||||||
|
# Operating System
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: NodeModules Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: '**/node_modules'
|
||||||
|
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: ./src/Ombi/ClientApp
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Publish to Chromatic
|
||||||
|
if: github.ref != 'refs/heads/master'
|
||||||
|
uses: chromaui/action@v1
|
||||||
|
with:
|
||||||
|
projectToken: 7c47e1a1a4bd
|
||||||
|
exitZeroOnChanges: true
|
||||||
|
workingDir: ./src/Ombi/ClientApp
|
||||||
|
buildScriptName: storybookbuild
|
||||||
|
exitOnceUploaded: true
|
||||||
|
|
||||||
|
- name: Publish to Chromatic and auto accept changes
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
uses: chromaui/action@v1
|
||||||
|
with:
|
||||||
|
projectToken: 7c47e1a1a4bd
|
||||||
|
autoAcceptChanges: true # 👈 Option to accept all changes
|
||||||
|
workingDir: ./src/Ombi/ClientApp
|
||||||
|
buildScriptName: storybookbuild
|
||||||
|
exitOnceUploaded: true
|
12
.github/workflows/cypress.yml
vendored
12
.github/workflows/cypress.yml
vendored
|
@ -34,15 +34,17 @@ jobs:
|
||||||
- name: Install Frontend Deps
|
- name: Install Frontend Deps
|
||||||
run: yarn --cwd ./src/Ombi/ClientApp install
|
run: yarn --cwd ./src/Ombi/ClientApp install
|
||||||
|
|
||||||
|
- name: Start Frontend
|
||||||
|
run: |
|
||||||
|
nohup yarn --cwd ./src/Ombi/ClientApp start &
|
||||||
|
|
||||||
- name: Install Automation Deps
|
- name: Install Automation Deps
|
||||||
run: yarn --cwd ./tests install
|
run: yarn --cwd ./tests install
|
||||||
|
|
||||||
- name: Start Backend
|
- name: Start Backend
|
||||||
run: |
|
run: |
|
||||||
nohup dotnet run --project ./src/Ombi -- --host http://*:3577 &
|
nohup dotnet run --project ./src/Ombi -- --host http://*:3577 &
|
||||||
- name: Start Frontend
|
|
||||||
run: |
|
|
||||||
nohup yarn --cwd ./src/Ombi/ClientApp start &
|
|
||||||
- name: Cypress Tests
|
- name: Cypress Tests
|
||||||
uses: cypress-io/github-action@v2.8.2
|
uses: cypress-io/github-action@v2.8.2
|
||||||
with:
|
with:
|
||||||
|
@ -51,8 +53,8 @@ jobs:
|
||||||
headless: true
|
headless: true
|
||||||
working-directory: tests
|
working-directory: tests
|
||||||
wait-on: http://localhost:3577/
|
wait-on: http://localhost:3577/
|
||||||
# 7 minutes
|
# 10 minutes
|
||||||
wait-on-timeout: 420
|
wait-on-timeout: 600
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
16
src/Ombi/ClientApp/.storybook/main.js
Normal file
16
src/Ombi/ClientApp/.storybook/main.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
"stories": [
|
||||||
|
"../src/**/*.stories.mdx",
|
||||||
|
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||||
|
],
|
||||||
|
"addons": [
|
||||||
|
"@storybook/addon-links",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@storybook/addon-interactions"
|
||||||
|
],
|
||||||
|
"framework": "@storybook/angular",
|
||||||
|
"core": {
|
||||||
|
"builder": "@storybook/builder-webpack5"
|
||||||
|
},
|
||||||
|
"staticDirs": ['../../wwwroot/images']
|
||||||
|
}
|
5
src/Ombi/ClientApp/.storybook/preview-body.html
Normal file
5
src/Ombi/ClientApp/.storybook/preview-body.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<style>
|
||||||
|
.test-class {
|
||||||
|
background-color: purple;
|
||||||
|
}
|
||||||
|
</style>
|
14
src/Ombi/ClientApp/.storybook/preview.js
Normal file
14
src/Ombi/ClientApp/.storybook/preview.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { setCompodocJson } from "@storybook/addon-docs/angular";
|
||||||
|
import docJson from "../documentation.json";
|
||||||
|
setCompodocJson(docJson);
|
||||||
|
|
||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
docs: { inlineStories: true },
|
||||||
|
}
|
24
src/Ombi/ClientApp/.storybook/tsconfig.json
Normal file
24
src/Ombi/ClientApp/.storybook/tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"extends": "../src/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"typeRoots": [
|
||||||
|
"../node_modules/@typings"
|
||||||
|
],
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"../src/test.ts",
|
||||||
|
"../src/**/*.spec.ts",
|
||||||
|
"../projects/**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"../src/**/*",
|
||||||
|
"../projects/**/*"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"./typings.d.ts"
|
||||||
|
]
|
||||||
|
}
|
4
src/Ombi/ClientApp/.storybook/typings.d.ts
vendored
Normal file
4
src/Ombi/ClientApp/.storybook/typings.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
declare module '*.md' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
18
src/Ombi/ClientApp/documentation.json
Normal file
18
src/Ombi/ClientApp/documentation.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"pipes": [],
|
||||||
|
"interfaces": [],
|
||||||
|
"injectables": [],
|
||||||
|
"guards": [],
|
||||||
|
"interceptors": [],
|
||||||
|
"classes": [],
|
||||||
|
"directives": [],
|
||||||
|
"components": [],
|
||||||
|
"modules": [],
|
||||||
|
"miscellaneous": [],
|
||||||
|
"routes": [],
|
||||||
|
"coverage": {
|
||||||
|
"count": 0,
|
||||||
|
"status": "low",
|
||||||
|
"files": []
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,11 @@
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --port 3578 --configuration hmr",
|
"start": "ng serve --port 3578 --configuration hmr",
|
||||||
"build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build -c production",
|
"build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build -c production",
|
||||||
"lint": "ng lint"
|
"lint": "ng lint",
|
||||||
|
"docs:json": "compodoc -p ./tsconfig.json -e json -d .",
|
||||||
|
"storybook": "start-storybook -p 6006",
|
||||||
|
"chromatic": "chromatic --exit-zero-on-changes",
|
||||||
|
"storybookbuild": "yarn build-storybook"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -22,7 +26,7 @@
|
||||||
"@angular/platform-server": "^14.0.0",
|
"@angular/platform-server": "^14.0.0",
|
||||||
"@angular/router": "^14.0.0",
|
"@angular/router": "^14.0.0",
|
||||||
"@angularclass/hmr": "^3.0.0",
|
"@angularclass/hmr": "^3.0.0",
|
||||||
"@aspnet/signalr": "^1.1.0",
|
"@microsoft/signalr": "^6.0.7",
|
||||||
"@auth0/angular-jwt": "^5.0.2",
|
"@auth0/angular-jwt": "^5.0.2",
|
||||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||||
"@fullcalendar/core": "^4.2.0",
|
"@fullcalendar/core": "^4.2.0",
|
||||||
|
@ -59,16 +63,31 @@
|
||||||
"ts-md5": "^1.2.7",
|
"ts-md5": "^1.2.7",
|
||||||
"tslib": "^1.10.0",
|
"tslib": "^1.10.0",
|
||||||
"tslint-angular": "^1.1.2",
|
"tslint-angular": "^1.1.2",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4",
|
||||||
|
"protractor": "~5.4.0",
|
||||||
|
"ts-node": "~5.0.1",
|
||||||
|
"tslint": "^5.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^14.0.0",
|
"@angular-devkit/build-angular": "^14.0.0",
|
||||||
"@angular/cli": "^14.0.0",
|
"@angular/cli": "^14.0.0",
|
||||||
"@angular/compiler-cli": "^14.0.0",
|
"@angular/compiler-cli": "^14.0.0",
|
||||||
"@angular/language-service": "^14.0.0",
|
"@angular/language-service": "^14.0.0",
|
||||||
|
"@babel/core": "^7.18.9",
|
||||||
|
"@compodoc/compodoc": "^1.1.19",
|
||||||
|
"@types/node": "^16.11.45",
|
||||||
|
"@storybook/addon-actions": "^6.5.9",
|
||||||
|
"@storybook/addon-essentials": "^6.5.9",
|
||||||
|
"@storybook/addon-interactions": "^6.5.9",
|
||||||
|
"@storybook/addon-links": "^6.5.9",
|
||||||
|
"@storybook/angular": "^6.5.9",
|
||||||
|
"@storybook/builder-webpack5": "^6.5.9",
|
||||||
|
"@storybook/manager-webpack5": "^6.5.9",
|
||||||
|
"@storybook/testing-library": "^0.0.13",
|
||||||
"@types/jasmine": "~3.6.7",
|
"@types/jasmine": "~3.6.7",
|
||||||
"@types/jasminewd2": "~2.0.8",
|
"@types/jasminewd2": "~2.0.8",
|
||||||
"@types/node": "^16.10.9",
|
"babel-loader": "^8.2.5",
|
||||||
|
"chromatic": "^6.7.1",
|
||||||
"codelyzer": "^6.0.1",
|
"codelyzer": "^6.0.1",
|
||||||
"typescript": "~4.7.3"
|
"typescript": "~4.7.3"
|
||||||
},
|
},
|
||||||
|
@ -76,5 +95,7 @@
|
||||||
"protractor": "~5.4.0",
|
"protractor": "~5.4.0",
|
||||||
"ts-node": "~5.0.1",
|
"ts-node": "~5.0.1",
|
||||||
"tslint": "^5.12.0"
|
"tslint": "^5.12.0"
|
||||||
}
|
},
|
||||||
}
|
"readme": "ERROR: No README data found!",
|
||||||
|
"_id": "ombi@3.0.0"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { Story, Meta, moduleMetadata } from '@storybook/angular';
|
||||||
|
import { RequestType } from '../../interfaces';
|
||||||
|
import { ImageComponent } from './image.component';
|
||||||
|
|
||||||
|
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||||
|
export default {
|
||||||
|
title: 'Image Component',
|
||||||
|
component: ImageComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_BASE_HREF,
|
||||||
|
useValue: ""
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||||
|
const Template: Story<ImageComponent> = (args: ImageComponent) => ({
|
||||||
|
props: args,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
Primary.args = {
|
||||||
|
src: 'https://ombi.io/img/logo-orange-small.png',
|
||||||
|
type: RequestType.movie
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClassApplied = Template.bind({});
|
||||||
|
ClassApplied.args = {
|
||||||
|
src: 'https://ombi.io/img/logo-orange-small.png',
|
||||||
|
type: RequestType.movie,
|
||||||
|
class: 'test-class'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StyleApplied = Template.bind({});
|
||||||
|
StyleApplied.args = {
|
||||||
|
src: 'https://ombi.io/img/logo-orange-small.png',
|
||||||
|
type: RequestType.movie,
|
||||||
|
style: 'background-color: red;'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdApplied = Template.bind({});
|
||||||
|
IdApplied.args = {
|
||||||
|
src: 'https://ombi.io/img/logo-orange-small.png',
|
||||||
|
type: RequestType.movie,
|
||||||
|
id: 'testId123'
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const InvalidMovieImage = Template.bind({});
|
||||||
|
// InvalidMovieImage.args = {
|
||||||
|
// src: 'https://httpstat.us/429',
|
||||||
|
// type: RequestType.movie,
|
||||||
|
// id: 'testId123'
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const InvalidTvImage = Template.bind({});
|
||||||
|
// InvalidTvImage.args = {
|
||||||
|
// src: 'https://httpstat.us/429',
|
||||||
|
// type: RequestType.tvShow,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const InvalidMusicImage = Template.bind({});
|
||||||
|
// InvalidMusicImage.args = {
|
||||||
|
// src: 'https://httpstat.us/429',
|
||||||
|
// type: RequestType.album,
|
||||||
|
// };
|
|
@ -1,198 +0,0 @@
|
||||||
<!-- Movie tab -->
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
|
||||||
|
|
||||||
<ng-template #FilterRef>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
|
||||||
{{ 'Search.Suggestions' | translate }}
|
|
||||||
<i class="fa-list fa-chevron-down"></i>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a (click)="popularMovies()" [translate]="'Search.Movies.PopularMovies'"></a></li>
|
|
||||||
<li><a (click)="upcomingMovies()" [translate]="'Search.Movies.UpcomingMovies'"></a></li>
|
|
||||||
<li><a (click)="topRatedMovies()" [translate]="'Search.Movies.TopRatedMovies'"></a></li>
|
|
||||||
<li><a (click)="nowPlayingMovies()" [translate]="'Search.Movies.NowPlayingMovies'"></a></li>
|
|
||||||
</ul>
|
|
||||||
<button class="btn btn-sm btn-primary-outline" (click)="refineOpen()">
|
|
||||||
{{ 'Search.Refine' | translate }}
|
|
||||||
<i class="fas" [ngClass]="{'fa-chevron-down': !refineSearchEnabled, 'fa-chevron-up': refineSearchEnabled}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div class="input-group search-bar-background">
|
|
||||||
<input id="search" type="text" placeholder="{{ 'Search.SearchBarPlaceholder' | translate}}"
|
|
||||||
class="form-control form-control-custom form-control-search form-control-withbuttons"
|
|
||||||
(keyup)="search($event)">
|
|
||||||
<div class="input-group-addon right-radius">
|
|
||||||
<div class="search-button-container-inline">
|
|
||||||
<ng-template [ngTemplateOutlet]="FilterRef"></ng-template>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row search-button-container">
|
|
||||||
<ng-template [ngTemplateOutlet]="FilterRef"></ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Refine search options -->
|
|
||||||
<div class="row top-spacing form-group vcenter" *ngIf="refineSearchEnabled">
|
|
||||||
<div class="col-md-1">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label">Year</label>
|
|
||||||
|
|
||||||
<input [(ngModel)]="searchYear" class="form-control form-control-custom refine-option">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <label for="name" class="col-xs-2 col-md-1">Language:</label> -->
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="select" class="control-label">Language</label>
|
|
||||||
<div id="profiles">
|
|
||||||
<select [(ngModel)]="selectedLanguage" class="form-control form-control-custom refine-option" id="select">
|
|
||||||
<option *ngFor="let lang of langauges" value="{{lang.code}}">{{lang.nativeName}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input type="checkbox" id="actorSearch" name="actorSearch" [(ngModel)]="actorSearch">
|
|
||||||
<label for="actorSearch" tooltipPosition="top" pTooltip="Search for movies by actor">Actor Search</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-7">
|
|
||||||
<button class="btn pull-right btn-success-outline" (click)="applyRefinedSearch()">Apply</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<remaining-requests [movie]="true" [quotaRefreshEvents]="movieRequested.asObservable()" #remainingFilms></remaining-requests>
|
|
||||||
|
|
||||||
<!-- Movie content -->
|
|
||||||
<div id="movieList">
|
|
||||||
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
|
|
||||||
<i class='fas fa-film no-search-results-icon'></i>
|
|
||||||
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngFor="let result of movieResults">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="myBg backdrop" [style.background-image]="result.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="result.posterPath" class="img-responsive poster movie-poster" src="{{result.posterPath}}"
|
|
||||||
alt="poster">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-8 small-padding">
|
|
||||||
<div>
|
|
||||||
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
|
|
||||||
<h4>{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
|
|
||||||
</a>
|
|
||||||
<span class="tags">
|
|
||||||
<span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{
|
|
||||||
'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal |
|
|
||||||
amDateFormat: 'LL'} }}</span>
|
|
||||||
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel"
|
|
||||||
target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate |
|
|
||||||
amLocal | amUserLocale | amDateFormat: 'LL'} }}</span>
|
|
||||||
|
|
||||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span
|
|
||||||
class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
|
|
||||||
|
|
||||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span
|
|
||||||
class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a>
|
|
||||||
<span *ngIf="result.quality" id="qualityLabel" class="label label-success">{{result.quality}}p</span>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="result.available"><span class="label label-success" id="availableLabel"
|
|
||||||
[translate]="'Common.Available'"></span></ng-template>
|
|
||||||
<ng-template [ngIf]="result.approved && !result.available"><span class="label label-info"
|
|
||||||
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
|
||||||
<ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning"
|
|
||||||
id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></ng-template>
|
|
||||||
<ng-template [ngIf]="!result.requested && !result.available && !result.approved"><span
|
|
||||||
class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span></ng-template>
|
|
||||||
|
|
||||||
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 0.9rem !important">{{result.overview}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-sm-2 small-padding">
|
|
||||||
<div *ngIf="result.available">
|
|
||||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fas fa-check"></i>
|
|
||||||
{{ 'Common.Available' | translate }}</button>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!result.available">
|
|
||||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
|
||||||
<ng-template #requestedBtn>
|
|
||||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i
|
|
||||||
class="fas fa-check"></i> {{ 'Common.Requested' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #notRequestedBtn>
|
|
||||||
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline"
|
|
||||||
(click)="request(result)">
|
|
||||||
<i *ngIf="result.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i> <i
|
|
||||||
*ngIf="!result.requestProcessing && !result.processed" class="fas fa-plus"></i>
|
|
||||||
<i *ngIf="result.processed && !result.requestProcessing" class="fas fa-check"></i> {{
|
|
||||||
'Common.Request' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="result.requested">
|
|
||||||
<a *ngIf="result.showSubscribe && !result.subscribed" style="text-align: right" class="btn btn btn-success-outline"
|
|
||||||
(click)="subscribe(result)" pTooltip="Subscribe for notifications when this movie becomes available">
|
|
||||||
<i class="fas fa-rss"></i> Subscribe</a>
|
|
||||||
<a *ngIf="result.showSubscribe && result.subscribed" style="text-align: right;" class="btn btn btn-warning-outline"
|
|
||||||
(click)="unSubscribe(result)" pTooltip="Unsubscribe notifications when this movie becomes available">
|
|
||||||
<i class="fas fa-rss"></i> Unsubscribe</a>
|
|
||||||
</div>
|
|
||||||
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)">
|
|
||||||
<i class="far fa-eye"></i> {{ 'Search.Similar' | translate }}</button>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div *ngIf="result.available">
|
|
||||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}"
|
|
||||||
target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnPlex' | translate}}</a>
|
|
||||||
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline"
|
|
||||||
href="{{result.embyUrl}}" target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnEmby' |
|
|
||||||
translate}}</a>
|
|
||||||
<a *ngIf="result.jellyfinUrl" style="text-align: right" id="jellyfinbtn" class="btn btn-sm btn-success-outline"
|
|
||||||
href="{{result.jellyfinUrl}}" target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnJellyfin' |
|
|
||||||
translate}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="true">
|
|
||||||
<i class="fas 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, result)">{{cat.value}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
|
||||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
|
@ -1,275 +0,0 @@
|
||||||
import { PlatformLocation, APP_BASE_HREF } from "@angular/common";
|
|
||||||
import { Component, Input, OnInit, Inject } from "@angular/core";
|
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
|
||||||
|
|
||||||
import { AuthService } from "../auth/auth.service";
|
|
||||||
import { IIssueCategory, ILanguageRefine, IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
|
||||||
import { NotificationService, RequestService, SearchService, SettingsService } from "../services";
|
|
||||||
|
|
||||||
import * as languageData from "../../other/iso-lang.json";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "movie-search",
|
|
||||||
templateUrl: "./moviesearch.component.html",
|
|
||||||
styleUrls: ["./search.component.scss"],
|
|
||||||
})
|
|
||||||
export class MovieSearchComponent implements OnInit {
|
|
||||||
|
|
||||||
public searchText: string;
|
|
||||||
public searchChanged: Subject<string> = new Subject<string>();
|
|
||||||
public movieRequested: Subject<void> = new Subject<void>();
|
|
||||||
public movieResults: ISearchMovieResult[];
|
|
||||||
public result: IRequestEngineResult;
|
|
||||||
|
|
||||||
public searchApplied = false;
|
|
||||||
public refineSearchEnabled = false;
|
|
||||||
public searchYear?: number;
|
|
||||||
public actorSearch: boolean;
|
|
||||||
public selectedLanguage: string;
|
|
||||||
public langauges: ILanguageRefine[];
|
|
||||||
|
|
||||||
@Input() public issueCategories: IIssueCategory[];
|
|
||||||
@Input() public issuesEnabled: boolean;
|
|
||||||
public issuesBarVisible = false;
|
|
||||||
public issueRequestTitle: string;
|
|
||||||
public issueRequestId: number;
|
|
||||||
public issueProviderId: string;
|
|
||||||
public issueCategorySelected: IIssueCategory;
|
|
||||||
public defaultPoster: string;
|
|
||||||
private href: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private searchService: SearchService, private requestService: RequestService,
|
|
||||||
private notificationService: NotificationService, private authService: AuthService,
|
|
||||||
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
|
|
||||||
@Inject(APP_BASE_HREF) href:string, private settingsService: SettingsService) {
|
|
||||||
this.href= href;
|
|
||||||
this.langauges = <ILanguageRefine[]><any>languageData;
|
|
||||||
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;
|
|
||||||
this.runSearch();
|
|
||||||
});
|
|
||||||
this.defaultPoster = "../../../images/default_movie_poster.png";
|
|
||||||
const base = this.href;
|
|
||||||
if (base) {
|
|
||||||
this.defaultPoster = "../../.." + base + "/images/default_movie_poster.png";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.searchText = "";
|
|
||||||
this.movieResults = [];
|
|
||||||
this.result = {
|
|
||||||
message: "",
|
|
||||||
result: false,
|
|
||||||
errorMessage: "",
|
|
||||||
};
|
|
||||||
this.settingsService.getDefaultLanguage().subscribe(x => this.selectedLanguage = x);
|
|
||||||
this.popularMovies();
|
|
||||||
}
|
|
||||||
|
|
||||||
public search(text: any) {
|
|
||||||
this.searchChanged.next(text.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public request(searchResult: ISearchMovieResult) {
|
|
||||||
searchResult.requested = true;
|
|
||||||
searchResult.requestProcessing = true;
|
|
||||||
searchResult.showSubscribe = false;
|
|
||||||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
|
|
||||||
searchResult.approved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const language = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : "en";
|
|
||||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: language })
|
|
||||||
.subscribe(x => {
|
|
||||||
this.result = x;
|
|
||||||
if (this.result.result) {
|
|
||||||
this.movieRequested.next();
|
|
||||||
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
|
|
||||||
this.notificationService.success(x);
|
|
||||||
searchResult.processed = true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (this.result.errorMessage && this.result.message) {
|
|
||||||
this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`);
|
|
||||||
} else {
|
|
||||||
this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage);
|
|
||||||
}
|
|
||||||
searchResult.requested = false;
|
|
||||||
searchResult.approved = false;
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
this.notificationService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public popularMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.popularMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public nowPlayingMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.nowPlayingMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public topRatedMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.topRatedMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public upcomingMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.upcomingMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) {
|
|
||||||
this.issueRequestId = req.id;
|
|
||||||
const releaseDate = new Date(req.releaseDate);
|
|
||||||
this.issueRequestTitle = req.title + ` (${releaseDate.getFullYear()})`;
|
|
||||||
this.issueCategorySelected = catId;
|
|
||||||
this.issuesBarVisible = true;
|
|
||||||
this.issueProviderId = req.id.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public similarMovies(theMovieDbId: number) {
|
|
||||||
this.clearResults();
|
|
||||||
const lang = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : "";
|
|
||||||
this.searchService.similarMovies(theMovieDbId, lang)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public subscribe(r: ISearchMovieResult) {
|
|
||||||
r.subscribed = true;
|
|
||||||
this.requestService.subscribeToMovie(r.requestId)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.notificationService.success(`Subscribed To Movie ${r.title}!`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public unSubscribe(r: ISearchMovieResult) {
|
|
||||||
r.subscribed = false;
|
|
||||||
this.requestService.unSubscribeToMovie(r.requestId)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.notificationService.success("Unsubscribed Movie!");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public refineOpen() {
|
|
||||||
this.refineSearchEnabled = !this.refineSearchEnabled;
|
|
||||||
if (!this.refineSearchEnabled) {
|
|
||||||
this.searchYear = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public applyRefinedSearch() {
|
|
||||||
this.runSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getExtraInfo() {
|
|
||||||
|
|
||||||
this.movieResults.forEach((val, index) => {
|
|
||||||
if (val.posterPath === null) {
|
|
||||||
val.posterPath = this.defaultPoster;
|
|
||||||
} else {
|
|
||||||
val.posterPath = "https://image.tmdb.org/t/p/w300/" + val.posterPath;
|
|
||||||
}
|
|
||||||
val.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")");
|
|
||||||
|
|
||||||
if (this.applyRefinedSearch) {
|
|
||||||
this.searchService.getMovieInformationWithRefined(val.id, this.selectedLanguage)
|
|
||||||
.subscribe(m => {
|
|
||||||
this.updateItem(val, m);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.searchService.getMovieInformation(val.id)
|
|
||||||
.subscribe(m => {
|
|
||||||
this.updateItem(val, m);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateItem(key: ISearchMovieResult, updated: ISearchMovieResult) {
|
|
||||||
const index = this.movieResults.indexOf(key, 0);
|
|
||||||
if (index > -1) {
|
|
||||||
const copy = { ...this.movieResults[index] };
|
|
||||||
this.movieResults[index] = updated;
|
|
||||||
this.movieResults[index].background = copy.background;
|
|
||||||
this.movieResults[index].posterPath = copy.posterPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private clearResults() {
|
|
||||||
this.movieResults = [];
|
|
||||||
this.searchApplied = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private runSearch() {
|
|
||||||
if (this.searchText === "") {
|
|
||||||
this.clearResults();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.refineOpen) {
|
|
||||||
if (!this.actorSearch) {
|
|
||||||
this.searchService.searchMovieWithRefined(this.searchText, this.searchYear, this.selectedLanguage)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
// Now let's load some extra info including IMDB Id
|
|
||||||
// This way the search is fast at displaying results.
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.searchService.searchMovieByActor(this.searchText, this.selectedLanguage)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
// Now let's load some extra info including IMDB Id
|
|
||||||
// This way the search is fast at displaying results.
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.searchService.searchMovie(this.searchText)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
// Now let's load some extra info including IMDB Id
|
|
||||||
// This way the search is fast at displaying results.
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
<!-- Movie tab -->
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
|
||||||
<div class="input-group-addon right-radius">
|
|
||||||
<div class="btn-group">
|
|
||||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
|
||||||
Suggestions
|
|
||||||
<i class="fas fa-chevron-down"></i>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a (click)="popularMovies()">Popular Movies</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="upcomingMovies()">Upcoming Movies</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="topRatedMovies()">Top Rated Movies</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="nowPlayingMovies()">Now Playing Movies</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<i id="movieSearchButton" class="fas fa-search"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<!-- Movie content -->
|
|
||||||
<div id="movieList">
|
|
||||||
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
|
|
||||||
<i class='fas fa-film no-search-results-icon'></i>
|
|
||||||
<div class='no-search-results-text'>Sorry, we didn't find any results!</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--NEW-->
|
|
||||||
<div *ngFor="let grid of movieResultGrid; let i = index">
|
|
||||||
<div class="row">
|
|
||||||
<div *ngFor="let r of grid.movies">
|
|
||||||
<div class="col-md-3">
|
|
||||||
|
|
||||||
<img *ngIf="r.posterPath" class="img-responsive poster" src="https://image.tmdb.org/t/p/w150/{{r.posterPath}}"
|
|
||||||
alt="poster">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--END NEW-->
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<div *ngFor="let result of movieResults">
|
|
||||||
<div class="row">
|
|
||||||
<div id="imgDiv" class="col-sm-2">
|
|
||||||
|
|
||||||
|
|
||||||
<img *ngIf="result.posterPath" class="img-responsive poster" src="https://image.tmdb.org/t/p/w150/{{result.posterPath}}"
|
|
||||||
alt="poster">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div>
|
|
||||||
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
|
|
||||||
<h4>{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<span *ngIf="result.releaseDate" class="label label-info" target="_blank">Release Date: {{result.releaseDate | amLocal | amUserLocale | amDateFormat: 'L'}}</span>
|
|
||||||
|
|
||||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homepageLabel" target="_blank">
|
|
||||||
<span class="label label-info">HomePage</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank">
|
|
||||||
<span class="label label-info">Trailer</span>
|
|
||||||
</a>
|
|
||||||
<span *ngIf="result.quality" class="label label-success" id="qualityLabel">{{result.quality}}p</span>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="result.available">
|
|
||||||
<span class="label label-success" id="availableLabel">Available</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.approved && !result.available">
|
|
||||||
<span class="label label-info" id="processingRequestLabel">Processing Request</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.requested && !result.approved && !result.available">
|
|
||||||
<span class="label label-warning" id="pendingApprovalLabel">Pending Approval</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="!result.requested && !result.available && !result.approved">
|
|
||||||
<span class="label label-danger" id="notRequetsedLabel">Not Requested</span>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 0.9rem !important">{{result.overview}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-sm-2">
|
|
||||||
|
|
||||||
<div *ngIf="result.available">
|
|
||||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
|
||||||
<i class="fas fa-check"></i> Available</button>
|
|
||||||
|
|
||||||
<div *ngIf="result.plexUrl">
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{result.plexUrl}}" target="_blank">
|
|
||||||
<i class="far fa-eye"></i> View In Plex</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!result.available">
|
|
||||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
|
||||||
<ng-template #requestedBtn>
|
|
||||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
|
|
||||||
<i class="fas fa-check"></i> Requested</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #notRequestedBtn>
|
|
||||||
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)">
|
|
||||||
<i *ngIf="result.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
|
|
||||||
<i *ngIf="!result.requestProcessing && !result.processed" class="fas fa-plus"></i>
|
|
||||||
<i *ngIf="result.processed && !result.requestProcessing" class="fas fa-check"></i>Request</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<div *ngIf="result.available">
|
|
||||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank">
|
|
||||||
<i class="far fa-eye"></i> View On Plex</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,164 +0,0 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
|
||||||
|
|
||||||
import { AuthService } from "../auth/auth.service";
|
|
||||||
import { IRequestEngineResult, ISearchMovieResult, ISearchMovieResultContainer } from "../interfaces";
|
|
||||||
import { NotificationService, RequestService, SearchService } from "../services";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "movie-search-grid",
|
|
||||||
templateUrl: "./moviesearchgrid.component.html",
|
|
||||||
})
|
|
||||||
export class MovieSearchGridComponent implements OnInit {
|
|
||||||
|
|
||||||
public searchText: string;
|
|
||||||
public searchChanged: Subject<string> = new Subject<string>();
|
|
||||||
public movieResults: ISearchMovieResult[];
|
|
||||||
public movieResultGrid: ISearchMovieResultContainer[] = [];
|
|
||||||
public result: IRequestEngineResult;
|
|
||||||
public searchApplied = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private searchService: SearchService, private requestService: RequestService,
|
|
||||||
private notificationService: NotificationService, private authService: AuthService) {
|
|
||||||
|
|
||||||
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();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.searchService.searchMovie(this.searchText)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
// Now let's load some exta info including IMDBId
|
|
||||||
// This way the search is fast at displaying results.
|
|
||||||
this.getExtaInfo();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.searchText = "";
|
|
||||||
this.movieResults = [];
|
|
||||||
this.result = {
|
|
||||||
message: "",
|
|
||||||
result: false,
|
|
||||||
errorMessage: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public search(text: any) {
|
|
||||||
this.searchChanged.next(text.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public request(searchResult: ISearchMovieResult) {
|
|
||||||
searchResult.requested = true;
|
|
||||||
searchResult.requestProcessing = true;
|
|
||||||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
|
|
||||||
searchResult.approved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: "en" })
|
|
||||||
.subscribe(x => {
|
|
||||||
this.result = x;
|
|
||||||
|
|
||||||
if (this.result.result) {
|
|
||||||
this.notificationService.success(
|
|
||||||
`Request for ${searchResult.title} has been added successfully`);
|
|
||||||
searchResult.processed = true;
|
|
||||||
} else {
|
|
||||||
if (this.result.errorMessage && this.result.message) {
|
|
||||||
this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`);
|
|
||||||
} else {
|
|
||||||
this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage);
|
|
||||||
}
|
|
||||||
searchResult.requested = false;
|
|
||||||
searchResult.approved = false;
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
this.notificationService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public popularMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.popularMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.processGrid(x);
|
|
||||||
this.getExtaInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public nowPlayingMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.nowPlayingMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtaInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public topRatedMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.topRatedMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtaInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public upcomingMovies() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.upcomingMovies()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.movieResults = x;
|
|
||||||
this.getExtaInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getExtaInfo() {
|
|
||||||
this.movieResults.forEach((val) => {
|
|
||||||
this.searchService.getMovieInformation(val.id)
|
|
||||||
.subscribe(m => this.updateItem(val, m));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateItem(key: ISearchMovieResult, updated: ISearchMovieResult) {
|
|
||||||
const index = this.movieResults.indexOf(key, 0);
|
|
||||||
if (index > -1) {
|
|
||||||
this.movieResults[index] = updated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearResults() {
|
|
||||||
this.movieResults = [];
|
|
||||||
this.searchApplied = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private processGrid(movies: ISearchMovieResult[]) {
|
|
||||||
let container = <ISearchMovieResultContainer> { movies: [] };
|
|
||||||
movies.forEach((movie, i) => {
|
|
||||||
i++;
|
|
||||||
if ((i % 4) === 0) {
|
|
||||||
container.movies.push(movie);
|
|
||||||
this.movieResultGrid.push(container);
|
|
||||||
container = <ISearchMovieResultContainer> { movies: [] };
|
|
||||||
} else {
|
|
||||||
container.movies.push(movie);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.movieResultGrid.push(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
<div class="row">
|
|
||||||
<!--Backdrop-->
|
|
||||||
<div class="album-bg backdrop" [style.background-image]="result.background"></div>
|
|
||||||
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
|
||||||
|
|
||||||
<!--Album Art-->
|
|
||||||
<div class="col-sm-12 small-padding">
|
|
||||||
<img *ngIf="result.disk" class="img-responsive poster album-cover" src="{{result.disk}}" alt="poster">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--Artist Title-->
|
|
||||||
<div class="col-sm-12 small-padding">
|
|
||||||
<div>
|
|
||||||
<h4>
|
|
||||||
<a href="" target="_blank">
|
|
||||||
{{result.title | truncate: 36}}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</h4>
|
|
||||||
<h5>
|
|
||||||
<a href="" (click)="selectArtist($event, result.foreignArtistId)">
|
|
||||||
{{result.artistName}}
|
|
||||||
</a>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--Tags-->
|
|
||||||
<div>
|
|
||||||
<span class="tags">
|
|
||||||
<!-- <span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }}</span>
|
|
||||||
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }}</span>
|
|
||||||
|
|
||||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
|
|
||||||
|
|
||||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a> -->
|
|
||||||
<ng-template [ngIf]="!result.requested && !result.fullyAvailable && !result.approved">
|
|
||||||
<span class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.fullyAvailable">
|
|
||||||
<span class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.partiallyAvailable">
|
|
||||||
<span class="label label-info" id="availableLabel" [translate]="'Common.PartiallyAvailable'"></span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.monitored && !result.fullyAvailable">
|
|
||||||
<span class="label label-info" id="processingRequestLabel" [translate]="'Common.Monitored'"></span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.requested && !result.approved && !result.partiallyAvailable">
|
|
||||||
<span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="result.approved && !result.fullyAvailable"><span class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ng-template [ngIf]="result.releaseDate">
|
|
||||||
<span class="label label-info" id="availableLabel">Release Date: {{result.releaseDate | amLocal | amUserLocale | amDateFormat: 'L'}}</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.rating">
|
|
||||||
<span class="label label-info" id="availableLabel">{{result.rating}}/10</span>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--Buttons-->
|
|
||||||
<div class="col-sm-12 small-padding">
|
|
||||||
<!-- <div class="row" *ngIf="result.requested">
|
|
||||||
<div class="col-md-2 col-md-push-10">
|
|
||||||
|
|
||||||
<a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fas fa-rss"></i></a>
|
|
||||||
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fas fa-rss"></i></a>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
<div *ngIf="result.fullyAvailable">
|
|
||||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
|
||||||
<i class="fas fa-check"></i> {{ 'Common.Available' | translate }}</button>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!result.fullyAvailable">
|
|
||||||
<div *ngIf="result.requested || result.approved || result.monitored; then requestedBtn else notRequestedBtn"></div>
|
|
||||||
<ng-template #requestedBtn>
|
|
||||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
|
|
||||||
<i class="fas fa-check"></i> {{ 'Common.Requested' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #notRequestedBtn>
|
|
||||||
<button style="text-align: right" class="btn btn-primary-outline" (click)="request(result)">
|
|
||||||
<i *ngIf="result.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
|
|
||||||
<i *ngIf="!result.requestProcessing && !result.processed" class="fas fa-plus"></i>
|
|
||||||
<i *ngIf="result.processed && !result.requestProcessing" class="fas fa-check"></i> {{ 'Common.Request'
|
|
||||||
| translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="dropdown" *ngIf="(result.partiallyAvailable || result.fullyAvailable) && issueCategories && issuesEnabled">
|
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="true">
|
|
||||||
<i class="fas fa-plus"></i> Report Issue
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
|
||||||
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, result)">{{cat.value}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
|
||||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
|
@ -1,91 +0,0 @@
|
||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
|
||||||
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
import { AuthService } from "../../auth/auth.service";
|
|
||||||
import { IIssueCategory, IRequestEngineResult } from "../../interfaces";
|
|
||||||
import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult";
|
|
||||||
import { NotificationService, RequestService } from "../../services";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "album-search",
|
|
||||||
templateUrl: "./albumsearch.component.html",
|
|
||||||
})
|
|
||||||
export class AlbumSearchComponent {
|
|
||||||
|
|
||||||
@Input() public result: ISearchAlbumResult;
|
|
||||||
public engineResult: IRequestEngineResult;
|
|
||||||
@Input() public defaultPoster: string;
|
|
||||||
|
|
||||||
@Input() public issueCategories: IIssueCategory[];
|
|
||||||
@Input() public issuesEnabled: boolean;
|
|
||||||
|
|
||||||
@Input() public musicRequested: Subject<void>;
|
|
||||||
public issuesBarVisible = false;
|
|
||||||
public issueRequestTitle: string;
|
|
||||||
public issueRequestId: number;
|
|
||||||
public issueProviderId: string;
|
|
||||||
public issueCategorySelected: IIssueCategory;
|
|
||||||
|
|
||||||
@Output() public setSearch = new EventEmitter<string>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private requestService: RequestService,
|
|
||||||
private notificationService: NotificationService, private authService: AuthService,
|
|
||||||
private readonly translate: TranslateService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectArtist(event: Event, artistId: string) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.setSearch.emit(artistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) {
|
|
||||||
this.issueRequestId = req.id;
|
|
||||||
this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`;
|
|
||||||
this.issueCategorySelected = catId;
|
|
||||||
this.issuesBarVisible = true;
|
|
||||||
this.issueProviderId = req.id.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public request(searchResult: ISearchAlbumResult) {
|
|
||||||
searchResult.requested = true;
|
|
||||||
searchResult.requestProcessing = true;
|
|
||||||
searchResult.showSubscribe = false;
|
|
||||||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMusic")) {
|
|
||||||
searchResult.approved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.requestService.requestAlbum({ foreignAlbumId: searchResult.foreignAlbumId })
|
|
||||||
.subscribe(x => {
|
|
||||||
|
|
||||||
this.engineResult = x;
|
|
||||||
|
|
||||||
if (this.engineResult.result) {
|
|
||||||
this.musicRequested.next();
|
|
||||||
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
|
|
||||||
this.notificationService.success(x);
|
|
||||||
searchResult.processed = true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (this.engineResult.errorMessage && this.engineResult.message) {
|
|
||||||
this.notificationService.warning("Request Added", `${this.engineResult.message} - ${this.engineResult.errorMessage}`);
|
|
||||||
} else {
|
|
||||||
this.notificationService.warning("Request Added", this.engineResult.message ? this.engineResult.message : this.engineResult.errorMessage);
|
|
||||||
}
|
|
||||||
searchResult.requested = false;
|
|
||||||
searchResult.approved = false;
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
searchResult.processed = false;
|
|
||||||
searchResult.requestProcessing = false;
|
|
||||||
this.notificationService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="myBg backdrop" [style.background-image]="result.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-3 small-padding">
|
|
||||||
<img *ngIf="result.poster" class="img-responsive poster artist-cover" src="{{result.poster}}" alt="poster">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-7 small-padding">
|
|
||||||
<div>
|
|
||||||
<a href="" target="_blank">
|
|
||||||
<h4>{{result.artistName}}</h4>
|
|
||||||
</a>
|
|
||||||
<span class="tags">
|
|
||||||
<!-- <span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }}</span>
|
|
||||||
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }}</span>
|
|
||||||
|
|
||||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
|
|
||||||
|
|
||||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a> -->
|
|
||||||
|
|
||||||
<ng-template [ngIf]="result.artistType">
|
|
||||||
<span class="label label-info" id="artistType">{{result.artistType}}</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.disambiguation">
|
|
||||||
<span class="label label-info" id="disambiguation">{{result.disambiguation}}</span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="result.monitored">
|
|
||||||
<span class="label label-info" id="disambiguation">Monitored</span>
|
|
||||||
</ng-template>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 0.9rem !important">{{result.overview | truncate: 350 }}</p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<a id="infoTags" *ngFor="let link of result.links" href="{{link.url}}" target="_blank" class="label label-primary">
|
|
||||||
|
|
||||||
{{link.name}}
|
|
||||||
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-2 small-padding">
|
|
||||||
<div class="row" *ngIf="result.requested">
|
|
||||||
<div class="col-md-2 col-md-push-10">
|
|
||||||
|
|
||||||
<!-- <a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fas fa-rss"></i></a>
|
|
||||||
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fas fa-rss"></i></a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button style="text-align: right" class="btn btn-info-outline" [disabled]="searchingAlbums" (click)="viewAllAlbums()">
|
|
||||||
<i class="far fa-eye"></i> View Albums</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- <button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)"> <i class="far fa-eye"></i> {{ 'Search.Similar' | translate }}</button> -->
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|
||||||
|
|
||||||
import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult";
|
|
||||||
import { SearchService } from "../../services";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "artist-search",
|
|
||||||
templateUrl: "./artistsearch.component.html",
|
|
||||||
})
|
|
||||||
export class ArtistSearchComponent {
|
|
||||||
|
|
||||||
@Input() public result: ISearchArtistResult;
|
|
||||||
@Input() public defaultPoster: string;
|
|
||||||
public searchingAlbums: boolean;
|
|
||||||
|
|
||||||
@Output() public viewAlbumsResult = new EventEmitter<ISearchAlbumResult[]>();
|
|
||||||
|
|
||||||
constructor(private searchService: SearchService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public viewAllAlbums() {
|
|
||||||
this.searchingAlbums = true;
|
|
||||||
this.searchService.getAlbumsForArtist(this.result.forignArtistId).subscribe(x => {
|
|
||||||
this.viewAlbumsResult.emit(x);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
<!-- Movie tab -->
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="search" type="text" placeholder="{{ 'Search.SearchBarPlaceholder' | translate }}" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
|
||||||
<div class="input-group-addon right-radius">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="radio">
|
|
||||||
<input type="radio" id="Album" name="Mode" [checked]="searchAlbum" (click)="searchMode(true)">
|
|
||||||
<label for="Album">Album Search</label>
|
|
||||||
<input type="radio" id="Artist" name="Mode" [checked]="!searchAlbum" (click)="searchMode(false)">
|
|
||||||
<label for="Artist">Artist Search</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<div id="movieList">
|
|
||||||
<div *ngIf="searchApplied && artistResult?.length <= 0 && !searchAlbum" class='no-search-results'>
|
|
||||||
<i class='fas fa-music no-search-results-icon'></i>
|
|
||||||
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="searchApplied && albumResult?.length <= 0 && searchAlbum" class='no-search-results'>
|
|
||||||
<i class='fas fa-music no-search-results-icon'></i>
|
|
||||||
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<remaining-requests [music]="true" [quotaRefreshEvents]="musicRequested.asObservable()" #remainingAlbums></remaining-requests>
|
|
||||||
|
|
||||||
<div *ngFor="let result of artistResult">
|
|
||||||
<artist-search [result]="result" [defaultPoster]="defaultPoster" (viewAlbumsResult)="viewAlbumsForArtist($event)"></artist-search>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div *ngFor="let result of albumResult" class="col-md-4">
|
|
||||||
<album-search [musicRequested]="musicRequested" [result]="result" [defaultPoster]="defaultPoster" (setSearch)="setArtistSearch($event)"></album-search>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
|
||||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
|
@ -1,149 +0,0 @@
|
||||||
import { PlatformLocation, APP_BASE_HREF } from "@angular/common";
|
|
||||||
import { Component, Input, OnInit, Inject } from "@angular/core";
|
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
|
||||||
import { IIssueCategory, IRequestEngineResult } from "../../interfaces";
|
|
||||||
import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult";
|
|
||||||
import { SearchService } from "../../services";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "music-search",
|
|
||||||
templateUrl: "./musicsearch.component.html",
|
|
||||||
})
|
|
||||||
export class MusicSearchComponent implements OnInit {
|
|
||||||
|
|
||||||
public searchText: string;
|
|
||||||
public searchChanged: Subject<string> = new Subject<string>();
|
|
||||||
public artistResult: ISearchArtistResult[];
|
|
||||||
public albumResult: ISearchAlbumResult[];
|
|
||||||
public result: IRequestEngineResult;
|
|
||||||
public searchApplied = false;
|
|
||||||
public searchAlbum: boolean = true;
|
|
||||||
|
|
||||||
public musicRequested: Subject<void> = new Subject<void>();
|
|
||||||
@Input() public issueCategories: IIssueCategory[];
|
|
||||||
@Input() public issuesEnabled: boolean;
|
|
||||||
public issuesBarVisible = false;
|
|
||||||
public issueRequestTitle: string;
|
|
||||||
public issueRequestId: number;
|
|
||||||
public issueProviderId: string;
|
|
||||||
public issueCategorySelected: IIssueCategory;
|
|
||||||
public defaultPoster: string;
|
|
||||||
|
|
||||||
private href: string;
|
|
||||||
constructor(
|
|
||||||
private searchService: SearchService, private sanitizer: DomSanitizer,
|
|
||||||
@Inject(APP_BASE_HREF) href:string) {
|
|
||||||
this.href = href;
|
|
||||||
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 === "") {
|
|
||||||
if(this.searchAlbum) {
|
|
||||||
this.clearAlbumResults();
|
|
||||||
} else {
|
|
||||||
this.clearArtistResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.searchAlbum) {
|
|
||||||
if(!this.searchText) {
|
|
||||||
this.searchText = "iowa"; // REMOVE
|
|
||||||
}
|
|
||||||
this.searchService.searchAlbum(this.searchText)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.albumResult = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
this.setAlbumBackground();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.searchService.searchArtist(this.searchText)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.artistResult = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
this.setArtistBackground();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.defaultPoster = "../../../images/default-music-placeholder.png";
|
|
||||||
const base = this.href;
|
|
||||||
if (base) {
|
|
||||||
this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.searchText = "";
|
|
||||||
this.artistResult = [];
|
|
||||||
this.result = {
|
|
||||||
message: "",
|
|
||||||
result: false,
|
|
||||||
errorMessage: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public search(text: any) {
|
|
||||||
this.searchChanged.next(text.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public searchMode(val: boolean) {
|
|
||||||
this.searchAlbum = val;
|
|
||||||
if(val) {
|
|
||||||
// Album
|
|
||||||
this.clearArtistResults();
|
|
||||||
} else {
|
|
||||||
this.clearAlbumResults();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public setArtistSearch(artistId: string) {
|
|
||||||
this.searchAlbum = false;
|
|
||||||
this.clearAlbumResults();
|
|
||||||
this.searchChanged.next(`lidarr:${artistId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public viewAlbumsForArtist(albums: ISearchAlbumResult[]) {
|
|
||||||
this.clearArtistResults();
|
|
||||||
this.searchAlbum = true;
|
|
||||||
this.albumResult = albums;
|
|
||||||
this.setAlbumBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearArtistResults() {
|
|
||||||
this.artistResult = [];
|
|
||||||
this.searchApplied = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearAlbumResults() {
|
|
||||||
this.albumResult = [];
|
|
||||||
this.searchApplied = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setArtistBackground() {
|
|
||||||
this.artistResult.forEach((val, index) => {
|
|
||||||
if (val.poster === null) {
|
|
||||||
val.poster = this.defaultPoster;
|
|
||||||
}
|
|
||||||
val.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("url(" + val.banner + ")");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setAlbumBackground() {
|
|
||||||
this.albumResult.forEach((val, index) => {
|
|
||||||
if (val.disk === null) {
|
|
||||||
if(val.cover === null) {
|
|
||||||
val.disk = this.defaultPoster;
|
|
||||||
} else {
|
|
||||||
val.disk = val.cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("url(" + val.cover + ")");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<h1 [translate]="'Search.Title'"></h1>
|
|
||||||
<h4 [translate]="'Search.Paragraph'"></h4>
|
|
||||||
<br />
|
|
||||||
<!-- Nav tabs -->
|
|
||||||
|
|
||||||
|
|
||||||
<ul id="nav-tabs" class="nav nav-tabs nav-justified" role="tablist">
|
|
||||||
|
|
||||||
<li role="presentation" class="active">
|
|
||||||
<a id="movieTabButton" href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab" (click)="selectMovieTab()"><i class="fas fa-film"></i> {{ 'Search.MoviesTab' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation">
|
|
||||||
<a id="tvTabButton" href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()"><i class="fas fa-tv"></i> {{ 'Search.TvTab' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" *ngIf="musicEnabled">
|
|
||||||
<a id="tvTabButton" href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()"><i class="fas fa-music"></i> {{ 'Search.MusicTab' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- Tab panes -->
|
|
||||||
<div class="tab-content">
|
|
||||||
|
|
||||||
<div [hidden]="!showMovie">
|
|
||||||
<movie-search [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></movie-search>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div [hidden]="!showTv">
|
|
||||||
<tv-search [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-search>
|
|
||||||
</div>
|
|
||||||
<div [hidden]="!showMusic">
|
|
||||||
<music-search></music-search>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
@media (max-width: 978px) {
|
|
||||||
.top-spacing {
|
|
||||||
padding-top: 5%
|
|
||||||
}
|
|
||||||
.form-control-search {
|
|
||||||
padding-right: 165px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@media (min-width: 979px) {
|
|
||||||
.top-spacing {
|
|
||||||
padding-top: 2%
|
|
||||||
}
|
|
||||||
.form-control-search {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-background {
|
|
||||||
background-color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcenter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refine-option {
|
|
||||||
box-shadow: inset 0 1px 5px rgba(0,0,0,1.0);
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
|
|
||||||
import { IIssueCategory } from "../interfaces";
|
|
||||||
import { IssuesService, SettingsService } from "../services";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: "./search.component.html",
|
|
||||||
})
|
|
||||||
export class SearchComponent implements OnInit {
|
|
||||||
public showTv: boolean;
|
|
||||||
public showMovie: boolean;
|
|
||||||
public showMusic: boolean;
|
|
||||||
public issueCategories: IIssueCategory[];
|
|
||||||
public issuesEnabled = false;
|
|
||||||
public musicEnabled: boolean;
|
|
||||||
|
|
||||||
constructor(private issuesService: IssuesService,
|
|
||||||
private settingsService: SettingsService) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x);
|
|
||||||
this.showMovie = true;
|
|
||||||
this.showTv = false;
|
|
||||||
this.showMusic = false;
|
|
||||||
this.issuesService.getCategories().subscribe(x => this.issueCategories = x);
|
|
||||||
this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectMovieTab() {
|
|
||||||
this.showMovie = true;
|
|
||||||
this.showTv = false;
|
|
||||||
this.showMusic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectTvTab() {
|
|
||||||
this.showMovie = false;
|
|
||||||
this.showTv = true;
|
|
||||||
this.showMusic = false;
|
|
||||||
}
|
|
||||||
public selectMusicTab() {
|
|
||||||
this.showMovie = false;
|
|
||||||
this.showTv = false;
|
|
||||||
this.showMusic = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
|
||||||
|
|
||||||
import { AlbumSearchComponent } from "./music/albumsearch.component";
|
|
||||||
import { ArtistSearchComponent } from "./music/artistsearch.component";
|
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
|
||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { FormsModule } from "@angular/forms";
|
|
||||||
import { MovieSearchComponent } from "./moviesearch.component";
|
|
||||||
import { MovieSearchGridComponent } from "./moviesearchgrid.component";
|
|
||||||
import { MusicSearchComponent } from "./music/musicsearch.component";
|
|
||||||
import { NgModule } from "@angular/core";
|
|
||||||
import { RemainingRequestsComponent } from "../requests/remainingrequests.component";
|
|
||||||
import { RequestService } from "../services";
|
|
||||||
import { SearchComponent } from "./search.component";
|
|
||||||
import { SearchService } from "../services";
|
|
||||||
import { SeriesInformationComponent } from "./seriesinformation.component";
|
|
||||||
import { SharedModule } from "../shared/shared.module";
|
|
||||||
import { TvSearchComponent } from "./tvsearch.component";
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{ path: "", component: SearchComponent, canActivate: [AuthGuard] },
|
|
||||||
{ path: "show/:id", component: SeriesInformationComponent, canActivate: [AuthGuard] },
|
|
||||||
];
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
TreeTableModule,
|
|
||||||
SharedModule,
|
|
||||||
SidebarModule,
|
|
||||||
TooltipModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
SearchComponent,
|
|
||||||
MovieSearchComponent,
|
|
||||||
TvSearchComponent,
|
|
||||||
SeriesInformationComponent,
|
|
||||||
MovieSearchGridComponent,
|
|
||||||
RemainingRequestsComponent,
|
|
||||||
MusicSearchComponent,
|
|
||||||
ArtistSearchComponent,
|
|
||||||
AlbumSearchComponent,
|
|
||||||
],
|
|
||||||
exports: [
|
|
||||||
RouterModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
SearchService,
|
|
||||||
RequestService,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class SearchModule { }
|
|
|
@ -1,71 +0,0 @@
|
||||||
<div *ngIf="series" class="content-space">
|
|
||||||
<button class="btn btn-sm btn-success pull-right" (click)="submitRequests()" title="Go to top">{{ 'Search.TvShows.SubmitRequest' | translate }}</button>
|
|
||||||
|
|
||||||
<ngb-tabset>
|
|
||||||
|
|
||||||
<div *ngFor="let season of series.seasonRequests">
|
|
||||||
<ngb-tab [id]="season.seasonNumber" [title]="season.seasonNumber">
|
|
||||||
<ng-template ngbTabContent>
|
|
||||||
<h2 [translate]="'Requests.SeasonNumberHeading'" [translateParams]="{seasonNumber: season.seasonNumber}">Season: {{season.seasonNumber}}</h2>
|
|
||||||
|
|
||||||
<button (click)="addAllEpisodes(season)" class="btn btn-sm btn-primary-outline" [translate]="'Search.TvShows.SelectAllInSeason'" [translateParams]="{seasonNumber: season.seasonNumber}">Select All in Season {{season.seasonNumber}}</button>
|
|
||||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<a>
|
|
||||||
#
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<a>
|
|
||||||
Title
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<a>
|
|
||||||
Air Date
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<a>
|
|
||||||
Status
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let ep of season.episodes">
|
|
||||||
<td>
|
|
||||||
{{ep.episodeNumber}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ep.title}}
|
|
||||||
</td>
|
|
||||||
<td *ngIf="ep.airDateDisplay != 'Unknown'">
|
|
||||||
{{ep.airDate | amLocal | amUserLocale | amDateFormat: 'L' }}
|
|
||||||
</td>
|
|
||||||
<td *ngIf="ep.airDateDisplay == 'Unknown'">
|
|
||||||
{{ep.airDateDisplay }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ng-template [ngIf]="ep.available"><span class="label label-success" id="availableLabel">Available</span></ng-template>
|
|
||||||
<ng-template [ngIf]="ep.approved && !ep.available "><span class="label label-info" id="processingRequestLabel">Processing Request</span></ng-template>
|
|
||||||
<ng-template [ngIf]="ep.selected"><span class="label label-info" id="selectedLabel">Selected</span></ng-template>
|
|
||||||
<ng-template [ngIf]="ep.requested && !ep.approved && !ep.available && !ep.selected"><span class="label label-warning" id="pendingApprovalLabel">Pending Approval</span></ng-template>
|
|
||||||
<ng-template [ngIf]="!ep.requested && !ep.available && !ep.approved"><span class="label label-danger" id="notRequetsedLabel">Not Requested</span></ng-template>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<button *ngIf="!ep.selected" (click)="addRequest(ep)" [disabled]="ep.available || ep.requested || ep.approved" class="btn btn-sm btn-primary-outline">Select</button>
|
|
||||||
<button *ngIf="ep.selected" (click)="removeRequest(ep)" class="btn btn-sm btn-primary-outline">Unselect</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</ng-template>
|
|
||||||
</ngb-tab>
|
|
||||||
</div>
|
|
||||||
</ngb-tabset>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,25 +0,0 @@
|
||||||
#requestFloatingBtn {
|
|
||||||
position: fixed; /* Fixed/sticky position */
|
|
||||||
bottom: 20px; /* Place the button at the bottom of the page */
|
|
||||||
right: 30px; /* Place the button 30px from the right */
|
|
||||||
z-index: 99; /* Make sure it does not overlap */
|
|
||||||
cursor: pointer; /* Add a mouse pointer on hover */
|
|
||||||
padding: 15px; /* Some padding */
|
|
||||||
border-radius: 10px; /* Rounded corners */
|
|
||||||
}
|
|
||||||
|
|
||||||
#requestFloatingBtn:hover {
|
|
||||||
background-color: #555; /* Add a dark-grey background on hover */
|
|
||||||
}
|
|
||||||
|
|
||||||
#bannerimage {
|
|
||||||
width: 758px;
|
|
||||||
height: 140px;
|
|
||||||
background-color: black;
|
|
||||||
background-position: center;
|
|
||||||
padding-bottom:30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-space {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
import { Component, Input, OnInit } from "@angular/core";
|
|
||||||
|
|
||||||
import { NotificationService } from "../services";
|
|
||||||
import { RequestService } from "../services";
|
|
||||||
import { SearchService } from "../services";
|
|
||||||
|
|
||||||
import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
|
|
||||||
import { IEpisodesRequests } from "../interfaces";
|
|
||||||
import { ISearchTvResult } from "../interfaces";
|
|
||||||
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "seriesinformation",
|
|
||||||
templateUrl: "./seriesinformation.component.html",
|
|
||||||
styleUrls: ["./seriesinformation.component.scss"],
|
|
||||||
})
|
|
||||||
export class SeriesInformationComponent implements OnInit {
|
|
||||||
|
|
||||||
public result: IRequestEngineResult;
|
|
||||||
public series: ISearchTvResult;
|
|
||||||
public requestedEpisodes: IEpisodesRequests[] = [];
|
|
||||||
@Input() public tvRequested: Subject<void>;
|
|
||||||
@Input() private seriesId: number;
|
|
||||||
|
|
||||||
constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { }
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.searchService.getShowInformation(this.seriesId)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.series = x;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public submitRequests() {
|
|
||||||
// Make sure something has been selected
|
|
||||||
const selected = this.series.seasonRequests.some((season) => {
|
|
||||||
return season.episodes.some((ep) => {
|
|
||||||
return ep.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};
|
|
||||||
viewModel.seasons = [];
|
|
||||||
this.series.seasonRequests.forEach((season) => {
|
|
||||||
const seasonsViewModel = <ISeasonsViewModel> {seasonNumber: season.seasonNumber, episodes: []};
|
|
||||||
season.episodes.forEach(ep => {
|
|
||||||
if (!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) {
|
|
||||||
if (ep.selected) {
|
|
||||||
seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewModel.seasons.push(seasonsViewModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.requestService.requestTv(viewModel)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvRequested.next();
|
|
||||||
this.result = x as IRequestEngineResult;
|
|
||||||
if (this.result.result) {
|
|
||||||
this.notificationService.success(
|
|
||||||
`Request for ${this.series.title} has been added successfully`);
|
|
||||||
|
|
||||||
this.series.seasonRequests.forEach((season) => {
|
|
||||||
season.episodes.forEach((ep) => {
|
|
||||||
ep.selected = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.notificationService.warning("Request Added", this.result.errorMessage ? this.result.errorMessage : this.result.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public addRequest(episode: IEpisodesRequests) {
|
|
||||||
episode.requested = true;
|
|
||||||
episode.selected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeRequest(episode: IEpisodesRequests) {
|
|
||||||
episode.requested = false;
|
|
||||||
episode.selected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addAllEpisodes(season: INewSeasonRequests) {
|
|
||||||
season.episodes.forEach((ep) => this.addRequest(ep));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
<!-- Movie tab -->
|
|
||||||
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
|
||||||
<ng-template #FilterRef>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
|
||||||
{{ 'Search.Suggestions' | translate }}
|
|
||||||
<i class="fas fa-chevron-down"></i>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a (click)="popularShows()">{{ 'Search.TvShows.Popular' | translate }} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="trendingShows()">{{ 'Search.TvShows.Trending' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="mostWatchedShows()">{{ 'Search.TvShows.MostWatched' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a (click)="anticipatedShows()">{{ 'Search.TvShows.MostAnticipated' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="search" type="text" placeholder="{{ 'Search.SearchBarPlaceholder' | translate }}"
|
|
||||||
class="form-control form-control-custom form-control-search form-control-withbuttons"
|
|
||||||
(keyup)="search($event)">
|
|
||||||
<div class="input-group-addon right-radius">
|
|
||||||
<div class="search-button-container-inline">
|
|
||||||
<ng-template [ngTemplateOutlet]="FilterRef"></ng-template>
|
|
||||||
</div>
|
|
||||||
<i id="tvSearchButton" class="fas fa-search"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row search-button-container">
|
|
||||||
<ng-template [ngTemplateOutlet]="FilterRef"></ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<remaining-requests [tv]="true" [quotaRefreshEvents]="tvRequested.asObservable()" #remainingTvShows></remaining-requests>
|
|
||||||
|
|
||||||
<!-- Movie content -->
|
|
||||||
<div id="actorMovieList">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TV content -->
|
|
||||||
<div id="tvList">
|
|
||||||
|
|
||||||
<div *ngIf="searchApplied && tvResults?.length <= 0" class='no-search-results'>
|
|
||||||
<i class='fas fa-film no-search-results-icon'></i>
|
|
||||||
<div class='no-search-results-text'>{{ 'Search.NoResults' | translate }}</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="tvResults" >
|
|
||||||
<div *ngFor="let node of tvResults">
|
|
||||||
<!--This is the section that holds the parent level search results set-->
|
|
||||||
<div *ngIf="node">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<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.banner" class="img-responsive poster tv-poster" width="150" [src]="node.banner" alt="poster">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-8 small-padding">
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<a *ngIf="node.imdbId" href="{{node.imdbId}}" target="_blank">
|
|
||||||
<h4>{{node.title}} ({{node.firstAired | amLocal | amDateFormat: 'YYYY'}})</h4>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
<span class="tags">
|
|
||||||
<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.trailer" id="trailerLabel" href="{{node.trailer}}" target="_blank">
|
|
||||||
<span class="label label-info">{{ 'Search.Movies.Trailer' | translate }}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<span *ngIf="node.status" class="label label-primary" id="statusLabel" target="_blank">{{node.status}}</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span *ngIf="node.firstAired" class="label label-info" target="_blank" id="airDateLabel">{{ 'Search.TvShows.AirDate' | translate }} {{node.firstAired | amLocal | amUserLocale | amDateFormat: 'L'}}</span>
|
|
||||||
|
|
||||||
<span *ngIf="node.network" class="label label-info" id="networkLabel" target="_blank">{{node.network}}</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span *ngIf="node.available" class="label label-success" id="availableLabel">{{ 'Common.Available' | translate }}</span>
|
|
||||||
|
|
||||||
<span *ngIf="node.partlyAvailable" class="label label-warning" id="partiallyAvailableLabel">{{ 'Common.PartlyAvailable' | translate }}</span>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
<p class="tv-overview">{{node.overview}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-sm-2 small-padding">
|
|
||||||
<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="fas fa-plus"></i> {{ 'Common.Request' | translate }}
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
|
||||||
<li>
|
|
||||||
<a href="#" (click)="allSeasons(node, $event)">{{ 'Search.TvShows.AllSeasons' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" (click)="firstSeason(node, $event)">{{ 'Search.TvShows.FirstSeason' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" (click)="latestSeason(node, $event)">{{ 'Search.TvShows.LatestSeason' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" (click)="openClosestTab(node, $event)">{{ 'Search.TvShows.Select' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="node.fullyAvailable">
|
|
||||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
|
||||||
<i class="fas fa-check"></i> {{ 'Common.Available' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<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="far fa-eye"></i> {{ 'Search.ViewOnPlex' | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<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="far fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="node.jellyfinUrl && node.available">
|
|
||||||
<a style="text-align: right" id="jellyfinbtn" class="btn btn-sm btn-success-outline" href="{{node.jellyfinUrl}}" target="_blank">
|
|
||||||
<i class="far fa-eye"></i> {{ 'Search.ViewOnJellyfin' | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled">
|
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="true">
|
|
||||||
<i class="fas 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)">{{cat.value}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!node.available">
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
|
|
||||||
<div *ngIf="node.open">
|
|
||||||
<seriesinformation [seriesId]="node.id" [tvRequested]="tvRequested"></seriesinformation>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<issue-report [movie]="false" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
|
||||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
|
@ -1,238 +0,0 @@
|
||||||
import { PlatformLocation, APP_BASE_HREF } from "@angular/common";
|
|
||||||
import { Component, Input, OnInit, Inject } from "@angular/core";
|
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
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";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "tv-search",
|
|
||||||
templateUrl: "./tvsearch.component.html",
|
|
||||||
styleUrls: ["./../requests/tvrequests.component.scss"],
|
|
||||||
})
|
|
||||||
export class TvSearchComponent implements OnInit {
|
|
||||||
|
|
||||||
public searchText: string;
|
|
||||||
public searchChanged = new Subject<string>();
|
|
||||||
public tvResults: ISearchTvResult[];
|
|
||||||
public tvRequested: Subject<void> = new Subject<void>();
|
|
||||||
public result: IRequestEngineResult;
|
|
||||||
public searchApplied = false;
|
|
||||||
public defaultPoster: string;
|
|
||||||
|
|
||||||
@Input() public issueCategories: IIssueCategory[];
|
|
||||||
@Input() public issuesEnabled: boolean;
|
|
||||||
public issuesBarVisible = false;
|
|
||||||
public issueRequestTitle: string;
|
|
||||||
public issueRequestId: number;
|
|
||||||
public issueProviderId: string;
|
|
||||||
public issueCategorySelected: IIssueCategory;
|
|
||||||
private href: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private searchService: SearchService, private requestService: RequestService,
|
|
||||||
private notificationService: NotificationService, private authService: AuthService,
|
|
||||||
private imageService: ImageService, private sanitizer: DomSanitizer,
|
|
||||||
@Inject(APP_BASE_HREF) href:string) {
|
|
||||||
this.href = href;
|
|
||||||
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.searchTv(this.searchText)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvResults = x;
|
|
||||||
this.searchApplied = true;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.defaultPoster = "../../../images/default_tv_poster.png";
|
|
||||||
const base = this.href;
|
|
||||||
if (base) {
|
|
||||||
this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public openClosestTab(node: ISearchTvResult,el: any) {
|
|
||||||
el.preventDefault();
|
|
||||||
node.open = !node.open;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.searchText = "";
|
|
||||||
this.tvResults = [];
|
|
||||||
this.result = {
|
|
||||||
message: "",
|
|
||||||
result: false,
|
|
||||||
errorMessage: "",
|
|
||||||
};
|
|
||||||
this.popularShows();
|
|
||||||
}
|
|
||||||
|
|
||||||
public search(text: any) {
|
|
||||||
this.searchChanged.next(text.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public popularShows() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.popularTv()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public trendingShows() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.trendingTv()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public mostWatchedShows() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.mostWatchedTv()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public anticipatedShows() {
|
|
||||||
this.clearResults();
|
|
||||||
this.searchService.anticipatedTv()
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvResults = x;
|
|
||||||
this.getExtraInfo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getExtraInfo() {
|
|
||||||
this.tvResults.forEach((val, index) => {
|
|
||||||
this.imageService.getTvBanner(val.id).subscribe(x => {
|
|
||||||
if (x) {
|
|
||||||
val.background = this.sanitizer.
|
|
||||||
bypassSecurityTrustStyle
|
|
||||||
("url(" + x + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.searchService.getShowInformation(val.id)
|
|
||||||
.subscribe(x => {
|
|
||||||
if (x) {
|
|
||||||
this.setDefaults(x);
|
|
||||||
this.updateItem(val, x);
|
|
||||||
} else {
|
|
||||||
const index = this.tvResults.indexOf(val, 0);
|
|
||||||
if (index > -1) {
|
|
||||||
this.tvResults.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public request(searchResult: ISearchTvResult) {
|
|
||||||
searchResult.requested = true;
|
|
||||||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
|
|
||||||
searchResult.approved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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: [] };
|
|
||||||
season.episodes.forEach(ep => {
|
|
||||||
if (!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) {
|
|
||||||
if (ep.requested) {
|
|
||||||
seasonsViewModel.episodes.push({ episodeNumber: ep.episodeNumber });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewModel.seasons.push(seasonsViewModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.requestService.requestTv(viewModel)
|
|
||||||
.subscribe(x => {
|
|
||||||
this.tvRequested.next();
|
|
||||||
this.result = x;
|
|
||||||
if (this.result.result) {
|
|
||||||
this.notificationService.success(
|
|
||||||
`Request for ${searchResult.title} has been added successfully`);
|
|
||||||
} else {
|
|
||||||
if (this.result.errorMessage && this.result.message) {
|
|
||||||
this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`);
|
|
||||||
} else {
|
|
||||||
this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public allSeasons(searchResult: ISearchTvResult, event: any) {
|
|
||||||
event.preventDefault();
|
|
||||||
searchResult.requestAll = true;
|
|
||||||
this.request(searchResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public firstSeason(searchResult: ISearchTvResult, event: any) {
|
|
||||||
event.preventDefault();
|
|
||||||
searchResult.firstSeason = true;
|
|
||||||
this.request(searchResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public latestSeason(searchResult: ISearchTvResult, event: any) {
|
|
||||||
event.preventDefault();
|
|
||||||
searchResult.latestSeason = true;
|
|
||||||
this.request(searchResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public reportIssue(catId: IIssueCategory, req: ISearchTvResult) {
|
|
||||||
this.issueRequestId = req.id;
|
|
||||||
const firstAiredDate = new Date(req.firstAired);
|
|
||||||
this.issueRequestTitle = req.title + ` (${firstAiredDate.getFullYear()})`;
|
|
||||||
this.issueCategorySelected = catId;
|
|
||||||
this.issuesBarVisible = true;
|
|
||||||
this.issueProviderId = req.id.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
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].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: ISearchTvResult) {
|
|
||||||
if (x.banner === null) {
|
|
||||||
x.banner = this.defaultPoster;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.imdbId === null) {
|
|
||||||
x.imdbId = "https://www.tvmaze.com/shows/" + x.seriesId;
|
|
||||||
} else {
|
|
||||||
x.imdbId = "http://www.imdb.com/title/" + x.imdbId + "/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearResults() {
|
|
||||||
this.tvResults = [];
|
|
||||||
this.searchApplied = false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Injectable, EventEmitter } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
import { HubConnection } from '@aspnet/signalr';
|
import { HubConnection } from '@microsoft/signalr';
|
||||||
import * as signalR from '@aspnet/signalr';
|
import * as signalR from '@microsoft/signalr';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignalRNotificationService {
|
export class SignalRNotificationService {
|
||||||
|
|
|
@ -10,9 +10,11 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"types": ["node"],
|
"types": [
|
||||||
"resolveJsonModule":true,
|
"node"
|
||||||
"allowSyntheticDefaultImports":true,
|
],
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"node_modules/@types"
|
"node_modules/@types"
|
||||||
],
|
],
|
||||||
|
@ -20,13 +22,15 @@
|
||||||
"es2017",
|
"es2017",
|
||||||
"dom"
|
"dom"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
,
|
|
||||||
"files": [
|
"files": [
|
||||||
"main.ts",
|
"main.ts",
|
||||||
"polyfills.ts"
|
"polyfills.ts"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.d.ts"
|
"src/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.stories.*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
2
src/Ombi/ClientApp/src/typings/globals.d.ts
vendored
2
src/Ombi/ClientApp/src/typings/globals.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
// Globals
|
// Globals
|
||||||
declare var __webpack_public_path__: any;
|
// declare var __webpack_public_path__: any;
|
||||||
// declare module "*.json" {
|
// declare module "*.json" {
|
||||||
// const value: any;
|
// const value: any;
|
||||||
// export default value;
|
// export default value;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue