@ -0,0 +1,15 @@
|
||||
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"
|
||||
}
|
||||
}
|
@ -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 },
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../src/tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": [
|
||||
"../src/test.ts",
|
||||
"../src/**/*.spec.ts",
|
||||
"../projects/**/*.spec.ts"
|
||||
],
|
||||
"include": [
|
||||
"../src/**/*",
|
||||
"../projects/**/*"
|
||||
],
|
||||
"files": [
|
||||
"./typings.d.ts"
|
||||
]
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
declare module '*.md' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
{
|
||||
"pipes": [],
|
||||
"interfaces": [],
|
||||
"injectables": [],
|
||||
"guards": [],
|
||||
"interceptors": [],
|
||||
"classes": [],
|
||||
"directives": [],
|
||||
"components": [],
|
||||
"modules": [],
|
||||
"miscellaneous": {
|
||||
"variables": [
|
||||
{
|
||||
"name": "bootstrap",
|
||||
"ctype": "miscellaneous",
|
||||
"subtype": "variable",
|
||||
"file": "src/main.ts",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "",
|
||||
"defaultValue": "() => platformBrowserDynamic().bootstrapModule(AppModule)"
|
||||
},
|
||||
{
|
||||
"name": "module",
|
||||
"ctype": "miscellaneous",
|
||||
"subtype": "variable",
|
||||
"file": "src/main.ts",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "any"
|
||||
}
|
||||
],
|
||||
"functions": [],
|
||||
"typealiases": [],
|
||||
"enumerations": [],
|
||||
"groupedVariables": {
|
||||
"src/main.ts": [
|
||||
{
|
||||
"name": "bootstrap",
|
||||
"ctype": "miscellaneous",
|
||||
"subtype": "variable",
|
||||
"file": "src/main.ts",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "",
|
||||
"defaultValue": "() => platformBrowserDynamic().bootstrapModule(AppModule)"
|
||||
},
|
||||
{
|
||||
"name": "module",
|
||||
"ctype": "miscellaneous",
|
||||
"subtype": "variable",
|
||||
"file": "src/main.ts",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "any"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupedFunctions": {},
|
||||
"groupedEnumerations": {},
|
||||
"groupedTypeAliases": {}
|
||||
},
|
||||
"routes": [],
|
||||
"coverage": {
|
||||
"count": 0,
|
||||
"status": "low",
|
||||
"files": [
|
||||
{
|
||||
"filePath": "src/main.ts",
|
||||
"type": "variable",
|
||||
"linktype": "miscellaneous",
|
||||
"linksubtype": "variable",
|
||||
"name": "bootstrap",
|
||||
"coveragePercent": 0,
|
||||
"coverageCount": "0/1",
|
||||
"status": "low"
|
||||
},
|
||||
{
|
||||
"filePath": "src/main.ts",
|
||||
"type": "variable",
|
||||
"linktype": "miscellaneous",
|
||||
"linksubtype": "variable",
|
||||
"name": "module",
|
||||
"coveragePercent": 0,
|
||||
"coverageCount": "0/1",
|
||||
"status": "low"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<p-skeleton id="cardLoading{{result.id}}" width="100%" height="315px"></p-skeleton>
|
||||
|
||||
<div id="result{{result.id}}" class="ombi-card dark-card c" [style.display]="hide ? 'none' : 'block'">
|
||||
<div class="card-top-info">
|
||||
<div class="top-left" id="type{{result.id}}">
|
||||
{{ 'Common.' + RequestType[result.type] | translate }}
|
||||
</div>
|
||||
<div class="{{getStatusClass()}} top-right" id="status{{result.id}}">
|
||||
<span class="indicator"></span><span class="indicator-text" id="availabilityStatus{{result.id}}">{{getAvailabilityStatus()}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<img [routerLink]="generateDetailsLink()" id="cardImage" src="{{result.posterPath}}" class="image"
|
||||
alt="{{result.title}}">
|
||||
<div [ngClass]="result.posterPath.includes('images/') ? 'middle-show' : 'middle'">
|
||||
<a class="poster-overlay" [routerLink]="generateDetailsLink()">
|
||||
<div class="summary">
|
||||
<div class="title" id="title{{result.id}}">{{result.title}}</div>
|
||||
<div class="small-text ellipsis" id="overview{{result.id}}">{{result.overview}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div [ngClass]="result.posterPath.includes('images/') ? 'button-request-container-show' : 'button-request-container'" class="row" *ngIf="!result.available && !result.approved && !result.requested">
|
||||
|
||||
<div *ngIf="is4kEnabled && requestable && result.type === RequestType.movie;then show4K else regular"></div>
|
||||
<ng-template #show4K>
|
||||
<div class="button-request poster-overlay">
|
||||
<button [matMenuTriggerFor]="menu" id="requestButton{{result.id}}{{result.type}}{{discoverType}}" mat-raised-button class="btn-ombi full-width poster-request-btn">
|
||||
<i *ngIf="!loading" class="fa-lg fas fa-cloud-download-alt"></i>
|
||||
<i *ngIf="loading" class="fas fa-spinner fa-pulse fa-2x fa-fw" aria-hidden="true"></i>
|
||||
{{'Common.Request' | translate }}
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item class="btn-ombi full-width poster-request-btn" (click)="request(false)">{{'Common.Request' | translate }}</button>
|
||||
<button mat-menu-item class="btn-ombi full-width poster-request-btn" (click)="request(true)">{{'Common.Request4K' | translate }}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #regular>
|
||||
<div class="button-request poster-overlay">
|
||||
<button id="requestButton{{result.id}}{{result.type}}{{discoverType}}" *ngIf="requestable" mat-raised-button class="btn-ombi full-width poster-request-btn" (click)="request($event, false)">
|
||||
<i *ngIf="!loading" class="fa-lg fas fa-cloud-download-alt"></i>
|
||||
<i *ngIf="loading" class="fas fa-spinner fa-pulse fa-2x fa-fw" aria-hidden="true"></i>
|
||||
{{'Common.Request' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,299 @@
|
||||
@import "~styles/variables.scss";
|
||||
|
||||
#cardImage {
|
||||
border-radius: 5px;
|
||||
object-fit:cover;
|
||||
}
|
||||
|
||||
.dark-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
// Changed height to 100% to make all cards the same height
|
||||
.card-spacing {
|
||||
margin-top: 10%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.rating {
|
||||
position: absolute;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.expand {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
|
||||
// Changed height to 100% to make all cards the same height
|
||||
.grow {
|
||||
transition: all .2s ease-in-out;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grow:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep mat-dialog-container.mat-dialog-container {
|
||||
// background-color: $ombi-primary;
|
||||
// color: white;
|
||||
border-radius: 2%
|
||||
}
|
||||
|
||||
|
||||
/* Title adjust for the Discover page */
|
||||
.mat-card-content h6 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Summary adjust for Discover page */
|
||||
.small,
|
||||
small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#cardImage {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.ombi-card {
|
||||
padding: 5px;
|
||||
height:100%;
|
||||
max-width: 200px;
|
||||
max-height: 295px;
|
||||
}
|
||||
::ng-deep .p-carousel-indicators {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
border-radius: 10px;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
transition: .5s ease;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.middle {
|
||||
transition: .5s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 67%;
|
||||
width: 90%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.middle-show {
|
||||
transition: .5s ease;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 67%;
|
||||
width: 90%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
|
||||
.c {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c:hover .image {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.c:hover .middle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
font-size: 11px;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 6;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-top-info{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
background-color: rgba(15,23,31,0.7);
|
||||
display:flex;
|
||||
justify-content: space-between;
|
||||
padding-top:0.6em;
|
||||
padding-bottom:0.3em;
|
||||
z-index:2;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
font-size: 14px;
|
||||
font-weight:200;
|
||||
padding-left: 0.5em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* common */
|
||||
.top-right{
|
||||
display:flex;
|
||||
font-weight:200;
|
||||
}
|
||||
|
||||
.top-right span.indicator, span.indicator-text {
|
||||
display: none;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,.2);
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.top-right span.indicator{
|
||||
padding-right: 0px;
|
||||
}
|
||||
.top-right span.indicator-text{
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.top-right span.indicator:before{
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
-moz-border-radius: 7.5px;
|
||||
-webkit-border-radius: 7.5px;
|
||||
border-radius: 7.5px;
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
.top-right span.indicator, span.indicator-text{
|
||||
display:block;
|
||||
}
|
||||
.top-right span.indicator:before{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.top-right.available span.indicator:before{
|
||||
background-color: #1DE9B6;
|
||||
}
|
||||
|
||||
.top-right.approved span.indicator:before{
|
||||
background-color: #ffd740;
|
||||
}
|
||||
|
||||
.top-right.denied span.indicator:before{
|
||||
background-color: #660202;
|
||||
}
|
||||
|
||||
.top-right.partly-available span.indicator:before{
|
||||
background-color: #ffd740;
|
||||
}
|
||||
|
||||
.top-right.requested span.indicator:before{
|
||||
background-color: #ff5722;
|
||||
}
|
||||
|
||||
::ng-deep a.poster-overlay{
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
a.poster-overlay:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px){
|
||||
.ellipsis{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.top-right span.indicator-text{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.top-right span.indicator{
|
||||
padding-right:1em;
|
||||
}
|
||||
}
|
||||
|
||||
.ombi-card #cardImage:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ombi-card .button-request-container{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: -36px;
|
||||
margin-bottom: 0px;
|
||||
opacity:0;
|
||||
transition: .5s ease;
|
||||
}
|
||||
.ombi-card .button-request-container-show{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: -36px;
|
||||
margin-bottom: 0px;
|
||||
opacity:1;
|
||||
transition: .5s ease;
|
||||
}
|
||||
|
||||
::ng-deep .ombi-card .button-request-container .button-request{
|
||||
padding-right:0px;
|
||||
padding-left:0px;
|
||||
width:100%;
|
||||
}
|
||||
::ng-deep .ombi-card .button-request-container-show .button-request{
|
||||
padding-right:0px;
|
||||
padding-left:0px;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.c:hover .button-request-container {
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
.btn-ombi{
|
||||
background-color:#293a4c;
|
||||
}
|
||||
|
||||
::ng-deep .mat-menu-panel {
|
||||
min-width: 190px !important;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { DiscoverType } from "../../discover/components/carousel-list/carousel-list.component";
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { IDiscoverCardResult } from "../../discover/interfaces";
|
||||
import { ISearchTvResultV2 } from "../../interfaces/ISearchTvResultV2";
|
||||
import { Output } from "@angular/core";
|
||||
import { RequestType } from "../../interfaces";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
selector: "ombi-card",
|
||||
templateUrl: "./card.component.html",
|
||||
styleUrls: ["./card.component.scss"],
|
||||
})
|
||||
export default class CardComponent {
|
||||
|
||||
@Input() public discoverType: DiscoverType;
|
||||
@Input() public result: IDiscoverCardResult;
|
||||
@Input() public isAdmin: boolean;
|
||||
@Input() public is4kEnabled: boolean = false;
|
||||
@Output() public requested: EventEmitter<boolean> = new EventEmitter();
|
||||
public RequestType = RequestType;
|
||||
public hide: boolean;
|
||||
public allow4KButton: boolean = false;
|
||||
|
||||
public requestable: boolean;
|
||||
|
||||
// This data is needed to open the dialog
|
||||
private tvSearchResult: ISearchTvResultV2;
|
||||
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
|
||||
public generateDetailsLink(): string {
|
||||
switch (this.result.type) {
|
||||
case RequestType.movie:
|
||||
return `/details/movie/${this.result.id}`;
|
||||
case RequestType.tvShow:
|
||||
return `/details/tv/${this.result.id}`;
|
||||
case RequestType.album: //Actually artist
|
||||
return `/details/artist/${this.result.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
public getStatusClass(): string {
|
||||
if (this.result.available) {
|
||||
return "available";
|
||||
}
|
||||
if (this.tvSearchResult?.partlyAvailable) {
|
||||
return "partly-available";
|
||||
}
|
||||
if (this.result.approved) {
|
||||
return "approved";
|
||||
}
|
||||
if (this.result.denied) {
|
||||
return "denied";
|
||||
}
|
||||
if (this.result.requested) {
|
||||
return "requested";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public getAvailabilityStatus(): string {
|
||||
if (this.result.available) {
|
||||
return this.translate.instant("Common.Available");
|
||||
}
|
||||
if (this.tvSearchResult?.partlyAvailable) {
|
||||
return this.translate.instant("Common.PartlyAvailable");
|
||||
}
|
||||
if (this.result.approved) {
|
||||
return this.translate.instant("Common.Approved");
|
||||
}
|
||||
if (this.result.denied) {
|
||||
return this.translate.instant("Common.Denied");
|
||||
}
|
||||
if (this.result.requested) {
|
||||
return this.translate.instant("Common.Pending");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public request(is4k: boolean) {
|
||||
this.requested.emit(is4k);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||
|
||||
import { Meta, Story, moduleMetadata } from '@storybook/angular';
|
||||
|
||||
import { AppModule } from '../../app.module';
|
||||
import Card from './card.component';
|
||||
import { StorybookTranslateModule } from '../../../stories/storybook-translate-module';
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Ombi/Card',
|
||||
component: Card,
|
||||
// More on argTypes: https://storybook.js.org/docs/angular/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [AppModule],
|
||||
}),
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||
const Template: Story<Card> = (args: Card) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||
Primary.args = {
|
||||
result: {
|
||||
title: 'Title',
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
import * as fromComponents from './';
|
||||
|
||||
import { CarouselModule } from 'primeng/carousel';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||
import { NgModule } from "@angular/core";
|
||||
import { PipeModule } from "../pipes/pipe.module";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { SkeletonModule } from 'primeng/skeleton';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
SkeletonModule,
|
||||
TranslateModule,
|
||||
RouterModule,
|
||||
CommonModule,
|
||||
],
|
||||
declarations: [
|
||||
...fromComponents.components
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
|
||||
})
|
||||
export class ComponentsModule { }
|
@ -0,0 +1,5 @@
|
||||
import { CardComponent } from "./card/card.component";
|
||||
|
||||
export const components: any[] = [
|
||||
CardComponent,
|
||||
];
|
@ -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,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,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,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;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||
import { Story, Meta } from '@storybook/angular/types-6-0';
|
||||
import Button from './button.component';
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
// More on argTypes: https://storybook.js.org/docs/angular/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||
const Template: Story<Button> = (args: Button) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||
Primary.args = {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import { moduleMetadata } from '@storybook/angular';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import type { Story, Meta } from '@storybook/angular';
|
||||
|
||||
import Button from './button.component';
|
||||
import Header from './header.component';
|
||||
|
||||
export default {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
declarations: [Button],
|
||||
imports: [CommonModule],
|
||||
}),
|
||||
],
|
||||
parameters: {
|
||||
// More on Story layout: https://storybook.js.org/docs/angular/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<Header> = (args: Header) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const LoggedIn = Template.bind({});
|
||||
LoggedIn.args = {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = Template.bind({});
|
||||
LoggedOut.args = {};
|
@ -0,0 +1,211 @@
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
import Code from './assets/code-brackets.svg';
|
||||
import Colors from './assets/colors.svg';
|
||||
import Comments from './assets/comments.svg';
|
||||
import Direction from './assets/direction.svg';
|
||||
import Flow from './assets/flow.svg';
|
||||
import Plugin from './assets/plugin.svg';
|
||||
import Repo from './assets/repo.svg';
|
||||
import StackAlt from './assets/stackalt.svg';
|
||||
|
||||
<Meta title="Example/Introduction" />
|
||||
|
||||
<style>{`
|
||||
.subheading {
|
||||
--mediumdark: '#999999';
|
||||
font-weight: 900;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
letter-spacing: 6px;
|
||||
line-height: 24px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 12px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.link-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 620px) {
|
||||
.link-list {
|
||||
row-gap: 20px;
|
||||
column-gap: 20px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast:none) {
|
||||
.link-list {
|
||||
display: -ms-grid;
|
||||
-ms-grid-columns: 1fr 1fr;
|
||||
-ms-grid-rows: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.link-item {
|
||||
display: block;
|
||||
padding: 20px 30px 20px 15px;
|
||||
border: 1px solid #00000010;
|
||||
border-radius: 5px;
|
||||
transition: background 150ms ease-out, border 150ms ease-out, transform 150ms ease-out;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
border-color: #1EA7FD50;
|
||||
transform: translate3d(0, -3px, 0);
|
||||
box-shadow: rgba(0, 0, 0, 0.08) 0 3px 10px 0;
|
||||
}
|
||||
|
||||
.link-item:active {
|
||||
border-color: #1EA7FD;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.link-item strong {
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.link-item img {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
margin-right: 15px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.link-item span {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #E7FDD8;
|
||||
color: #66BF3C;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.tip-wrapper code {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
`}</style>
|
||||
|
||||
# Welcome to Storybook
|
||||
|
||||
Storybook helps you build UI components in isolation from your app's business logic, data, and context.
|
||||
That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA.
|
||||
|
||||
Browse example stories now by navigating to them in the sidebar.
|
||||
View their code in the `src/stories` directory to learn how they work.
|
||||
We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages.
|
||||
|
||||
<div className="subheading">Configure</div>
|
||||
|
||||
<div className="link-list">
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/addons/addon-types"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Plugin} alt="plugin" />
|
||||
<span>
|
||||
<strong>Presets for popular tools</strong>
|
||||
Easy setup for TypeScript, SCSS and more.
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/configure/webpack"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={StackAlt} alt="Build" />
|
||||
<span>
|
||||
<strong>Build configuration</strong>
|
||||
How to customize webpack and Babel
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/configure/styling-and-css"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Colors} alt="colors" />
|
||||
<span>
|
||||
<strong>Styling</strong>
|
||||
How to load and configure CSS libraries
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/get-started/setup#configure-storybook-for-your-stack"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Flow} alt="flow" />
|
||||
<span>
|
||||
<strong>Data</strong>
|
||||
Providers and mocking for data libraries
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="subheading">Learn</div>
|
||||
|
||||
<div className="link-list">
|
||||
<a className="link-item" href="https://storybook.js.org/docs" target="_blank">
|
||||
<img src={Repo} alt="repo" />
|
||||
<span>
|
||||
<strong>Storybook documentation</strong>
|
||||
Configure, customize, and extend
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://storybook.js.org/tutorials/" target="_blank">
|
||||
<img src={Direction} alt="direction" />
|
||||
<span>
|
||||
<strong>In-depth guides</strong>
|
||||
Best practices from leading teams
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://github.com/storybookjs/storybook" target="_blank">
|
||||
<img src={Code} alt="code" />
|
||||
<span>
|
||||
<strong>GitHub project</strong>
|
||||
View the source and add issues
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://discord.gg/storybook" target="_blank">
|
||||
<img src={Comments} alt="comments" />
|
||||
<span>
|
||||
<strong>Discord chat</strong>
|
||||
Chat with maintainers and the community
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span>Edit the Markdown in{' '}
|
||||
<code>src/stories/Introduction.stories.mdx</code>
|
||||
</div>
|
@ -0,0 +1,36 @@
|
||||
import { moduleMetadata, Story, Meta } from '@storybook/angular';
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import Button from './button.component';
|
||||
import Header from './header.component';
|
||||
import Page from './page.component';
|
||||
|
||||
export default {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on Story layout: https://storybook.js.org/docs/angular/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
declarations: [Button, Header],
|
||||
imports: [CommonModule],
|
||||
}),
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<Page> = (args: Page) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const LoggedOut = Template.bind({});
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/angular/writing-tests/interaction-testing
|
||||
export const LoggedIn = Template.bind({});
|
||||
LoggedIn.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = await canvas.getByRole('button', { name: /Log in/i });
|
||||
await userEvent.click(loginButton);
|
||||
};
|
@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface User {}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,53 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'storybook-button',
|
||||
template: ` <button
|
||||
type="button"
|
||||
(click)="onClick.emit($event)"
|
||||
[ngClass]="classes"
|
||||
[ngStyle]="{ 'background-color': backgroundColor }"
|
||||
>
|
||||
{{ label }}
|
||||
</button>`,
|
||||
styleUrls: ['./button.css'],
|
||||
})
|
||||
export default class ButtonComponent {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
@Input()
|
||||
primary = false;
|
||||
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
@Input()
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
@Input()
|
||||
size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/**
|
||||
* Button contents
|
||||
*
|
||||
* @required
|
||||
*/
|
||||
@Input()
|
||||
label = 'Button';
|
||||
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
@Output()
|
||||
onClick = new EventEmitter<Event>();
|
||||
|
||||
public get classes(): string[] {
|
||||
const mode = this.primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
|
||||
return ['storybook-button', `storybook-button--${this.size}`, mode];
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
.storybook-button {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 700;
|
||||
border: 0;
|
||||
border-radius: 3em;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
.storybook-button--primary {
|
||||
color: white;
|
||||
background-color: #1ea7fd;
|
||||
}
|
||||
.storybook-button--secondary {
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
||||
}
|
||||
.storybook-button--small {
|
||||
font-size: 12px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.storybook-button--medium {
|
||||
font-size: 14px;
|
||||
padding: 11px 20px;
|
||||
}
|
||||
.storybook-button--large {
|
||||
font-size: 16px;
|
||||
padding: 12px 24px;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// // also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||
|
||||
// import { Meta, Story } from '@storybook/angular/types-6-0';
|
||||
|
||||
// import Card from '../app/components/card/card.component';
|
||||
|
||||
// // More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||
// export default {
|
||||
// title: 'Ombi/Card',
|
||||
// component: Card,
|
||||
// // More on argTypes: https://storybook.js.org/docs/angular/api/argtypes
|
||||
// argTypes: {
|
||||
// backgroundColor: { control: 'color' },
|
||||
// },
|
||||
// } as Meta;
|
||||
|
||||
// // More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||
// const Template: Story<Card> = (args: Card) => ({
|
||||
// props: args,
|
||||
// });
|
||||
|
||||
// export const Primary = Template.bind({});
|
||||
// // More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||
// Primary.args = {
|
||||
// primary: true,
|
||||
// label: 'Buttonaaaa',
|
||||
// };
|
||||
|
||||
// export const Secondary = Template.bind({});
|
||||
// Secondary.args = {
|
||||
// label: 'Buttonaaaa',
|
||||
// };
|
||||
|
||||
// export const Large = Template.bind({});
|
||||
// Large.args = {
|
||||
// size: 'large',
|
||||
// label: 'Buttonaaa',
|
||||
// };
|
||||
|
||||
// export const Small = Template.bind({});
|
||||
// Small.args = {
|
||||
// size: 'small',
|
||||
// label: 'Buttonaaa',
|
||||
// };
|
@ -0,0 +1,75 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { User } from './User';
|
||||
|
||||
@Component({
|
||||
selector: 'storybook-header',
|
||||
template: `<header>
|
||||
<div class="wrapper">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div *ngIf="user">
|
||||
<span class="welcome">
|
||||
Welcome, <b>{{ user.name }}</b
|
||||
>!
|
||||
</span>
|
||||
<storybook-button
|
||||
*ngIf="user"
|
||||
size="small"
|
||||
(onClick)="onLogout.emit($event)"
|
||||
label="Log out"
|
||||
></storybook-button>
|
||||
</div>
|
||||
<div *ngIf="!user">
|
||||
<storybook-button
|
||||
*ngIf="!user"
|
||||
size="small"
|
||||
class="margin-left"
|
||||
(onClick)="onLogin.emit($event)"
|
||||
label="Log in"
|
||||
></storybook-button>
|
||||
<storybook-button
|
||||
*ngIf="!user"
|
||||
primary
|
||||
size="small"
|
||||
primary="true"
|
||||
class="margin-left"
|
||||
(onClick)="onCreateAccount.emit($event)"
|
||||
label="Sign up"
|
||||
></storybook-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>`,
|
||||
styleUrls: ['./header.css'],
|
||||
})
|
||||
export default class HeaderComponent {
|
||||
@Input()
|
||||
user: User | null = null;
|
||||
|
||||
@Output()
|
||||
onLogin = new EventEmitter<Event>();
|
||||
|
||||
@Output()
|
||||
onLogout = new EventEmitter<Event>();
|
||||
|
||||
@Output()
|
||||
onCreateAccount = new EventEmitter<Event>();
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
.wrapper {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 900;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
margin: 6px 0 6px 10px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
button + button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { User } from './User';
|
||||
|
||||
@Component({
|
||||
selector: 'storybook-page',
|
||||
template: `<article>
|
||||
<storybook-header
|
||||
[user]="user"
|
||||
(onLogout)="doLogout()"
|
||||
(onLogin)="doLogin()"
|
||||
(onCreateAccount)="doCreateAccount()"
|
||||
></storybook-header>
|
||||
<section>
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page data
|
||||
in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer"> docs </a>
|
||||
.
|
||||
</p>
|
||||
<div class="tip-wrapper">
|
||||
<span class="tip">Tip</span> Adjust the width of the canvas with the
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>`,
|
||||
styleUrls: ['./page.css'],
|
||||
})
|
||||
export default class PageComponent {
|
||||
user: User | null = null;
|
||||
|
||||
doLogout() {
|
||||
this.user = null;
|
||||
}
|
||||
|
||||
doLogin() {
|
||||
this.user = { name: 'Jane Doe' };
|
||||
}
|
||||
|
||||
doCreateAccount() {
|
||||
this.user = { name: 'Jane Doe' };
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
section {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding: 48px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-weight: 900;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
margin: 0 0 4px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
section p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
section a {
|
||||
text-decoration: none;
|
||||
color: #1ea7fd;
|
||||
}
|
||||
|
||||
section ul {
|
||||
padding-left: 30px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
section li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
section .tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #e7fdd8;
|
||||
color: #66bf3c;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
section .tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
section .tip-wrapper svg {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
section .tip-wrapper svg path {
|
||||
fill: #1ea7fd;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { TranslateLoader, TranslateModule, TranslateService } from "@ngx-translate/core";
|
||||
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { HttpLoaderFactory } from "../app/app.module";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { PlatformLocation } from "@angular/common";
|
||||
|
||||
/**
|
||||
A utility module adding I18N support for Storybook stories
|
||||
**/
|
||||
@NgModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient, PlatformLocation],
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class StorybookTranslateModule {
|
||||
constructor(translateService: TranslateService) {
|
||||
console.log("Configuring the translation service: ", translateService);
|
||||
console.log("Translations: ", translateService.translations);
|
||||
translateService.setDefaultLang("en-US");
|
||||
translateService.use("en-US");
|
||||
}
|
||||
}
|