mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: clean up layout, simplify markup, fix img alt
Remove unused .link-input styles from player-dialog component and tidy vod-details template indentation and structure. Reformat container and image/detail sections to use consistent indentation, add an alt attribute to the movie poster img, replace several label elements with simpler divs for clearer semantics, and ensure placeholder-cover fallback remains. These changes improve readability, reduce unused CSS, and enhance HTML accessibility.
This commit is contained in:
1 parent
3c4af147a8
commit
3455ff75f1
11 files changed
+169
-279
No files matched your search
@@ -12,20 +12,16 @@ import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { MockComponent, MockModule, MockPipe, MockProviders } from 'ng-mocks';
|
||||
import { NgxIndexedDBService } from 'ngx-indexed-db';
|
||||
import { NgxWhatsNewComponent } from 'ngx-whats-new';
|
||||
import { of } from 'rxjs';
|
||||
import { DataService } from '../../../../libs/services/src/lib/data.service';
|
||||
import { PlaylistsService } from '../../../../libs/services/src/lib/playlists.service';
|
||||
import { Language } from '../../../../libs/shared/interfaces/src/lib/language.enum';
|
||||
import { STORE_KEY } from '../../../../libs/shared/interfaces/src/lib/store-keys.enum';
|
||||
import { Theme } from '../../../../libs/shared/interfaces/src/lib/theme.enum';
|
||||
import { DataService, PlaylistsService } from 'services';
|
||||
import { Language, STORE_KEY, Theme } from 'shared-interfaces';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ElectronServiceStub } from './services/electron.service.stub';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { WhatsNewService } from './services/whats-new.service';
|
||||
import { WhatsNewServiceStub } from './services/whats-new.service.stub';
|
||||
|
||||
jest.spyOn(global.console, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(global.console, 'error').mockImplementation(() => {
|
||||
// suppress console.error output during tests
|
||||
});
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent;
|
||||
@@ -33,14 +29,12 @@ describe('AppComponent', () => {
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
let settingsService: SettingsService;
|
||||
let translateService: TranslateService;
|
||||
let whatsNewService: WhatsNewService;
|
||||
const defaultLanguage = 'en';
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent, MockPipe(TranslatePipe)],
|
||||
providers: [
|
||||
{ provide: WhatsNewService, useClass: WhatsNewServiceStub },
|
||||
MockProviders(
|
||||
TranslateService,
|
||||
PlaylistsService,
|
||||
@@ -68,7 +62,6 @@ describe('AppComponent', () => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
translateService = TestBed.inject(TranslateService);
|
||||
whatsNewService = TestBed.inject(WhatsNewService);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
// TODO: investigate in detail
|
||||
@@ -128,14 +121,6 @@ describe('AppComponent', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(route);
|
||||
}
|
||||
));
|
||||
|
||||
it('show show whats new dialog', () => {
|
||||
jest.spyOn(whatsNewService, 'getModalsByVersion');
|
||||
jest.spyOn(component, 'setDialogVisibility');
|
||||
component.showWhatsNewDialog();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalledTimes(1);
|
||||
expect(component.setDialogVisibility).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test version handling', () => {
|
||||
@@ -144,17 +129,8 @@ describe('AppComponent', () => {
|
||||
const spyOnSettingsGet = jest
|
||||
.spyOn(settingsService, 'getValueFromLocalStorage')
|
||||
.mockReturnValue(of(currentAppVersion));
|
||||
jest.spyOn(whatsNewService, 'getModalsByVersion').mockReturnValue([
|
||||
{},
|
||||
]);
|
||||
jest.spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
|
||||
component.handleWhatsNewDialog();
|
||||
expect(spyOnSettingsGet).toHaveBeenCalled();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalled();
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should get actual app version which is not outdated and do not shop updates dialog', () => {
|
||||
@@ -162,40 +138,8 @@ describe('AppComponent', () => {
|
||||
const spyOnSettingsGet = jest
|
||||
.spyOn(settingsService, 'getValueFromLocalStorage')
|
||||
.mockReturnValue(of(currentAppVersion));
|
||||
jest.spyOn(whatsNewService, 'getModalsByVersion').mockReturnValue([
|
||||
{},
|
||||
]);
|
||||
jest.spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
|
||||
component.handleWhatsNewDialog();
|
||||
expect(spyOnSettingsGet).toHaveBeenCalled();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalledTimes(0);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should change the visibility of the whats new dialog', () => {
|
||||
jest.spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
component.modals = [{}, {}];
|
||||
const visibilityFlag = true;
|
||||
component.setDialogVisibility(true);
|
||||
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledWith(visibilityFlag);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not change the visibility of the whats new dialog', () => {
|
||||
jest.spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
component.modals = [];
|
||||
component.setDialogVisibility(true);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+3
-6
@@ -1,6 +1,5 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
@@ -16,13 +15,11 @@ import { XtreamStore } from '../xtream.store';
|
||||
templateUrl: './category-content-view.component.html',
|
||||
styleUrls: ['./category-content-view.component.scss'],
|
||||
imports: [
|
||||
MatCardModule,
|
||||
MatPaginatorModule,
|
||||
GridListComponent,
|
||||
PlaylistErrorViewComponent,
|
||||
StalkerSeriesViewComponent,
|
||||
TranslatePipe,
|
||||
VodDetailsComponent,
|
||||
GridListComponent,
|
||||
StalkerSeriesViewComponent,
|
||||
],
|
||||
})
|
||||
export class CategoryContentViewComponent implements OnInit {
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
<h2 mat-dialog-title>{{ title }}</h2>
|
||||
<mat-dialog-content class="content">
|
||||
<app-web-player-view [streamUrl]="streamUrl" />
|
||||
<mat-form-field class="link-input">
|
||||
<input
|
||||
matInput
|
||||
formControlName="streamUrl"
|
||||
[value]="streamUrl"
|
||||
disabled
|
||||
/>
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="align-actions">
|
||||
<button
|
||||
mat-button
|
||||
[cdkCopyToClipboard]="streamUrl"
|
||||
(click)="showCopyNotification()"
|
||||
[matTooltip]="streamUrl"
|
||||
>
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
{{ 'PORTALS.COPY_STREAM_URL' | translate }}
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.link-input {
|
||||
width: 100%;
|
||||
padding-top: 5px;
|
||||
@include mat.form-field-density(-5);
|
||||
}
|
||||
|
||||
.align-actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import { Component, Inject, ViewEncapsulation } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { Component, inject, ViewEncapsulation } from '@angular/core';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { WebPlayerViewComponent } from 'shared-portals';
|
||||
|
||||
export interface PlayerDialogData {
|
||||
@@ -18,28 +17,27 @@ export interface PlayerDialogData {
|
||||
templateUrl: './player-dialog.component.html',
|
||||
imports: [
|
||||
ClipboardModule,
|
||||
MatButtonModule,
|
||||
MatButton,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
TranslateModule,
|
||||
MatIcon,
|
||||
MatTooltip,
|
||||
TranslatePipe,
|
||||
WebPlayerViewComponent,
|
||||
],
|
||||
styleUrl: './player-dialog.component.scss',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class PlayerDialogComponent {
|
||||
title: string;
|
||||
streamUrl: string;
|
||||
readonly data = inject<PlayerDialogData>(MAT_DIALOG_DATA);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: PlayerDialogData,
|
||||
private snackBar: MatSnackBar,
|
||||
private translateService: TranslateService
|
||||
) {
|
||||
this.streamUrl = data.streamUrl;
|
||||
this.title = data.title;
|
||||
readonly title: string;
|
||||
readonly streamUrl: string;
|
||||
|
||||
constructor() {
|
||||
this.streamUrl = this.data.streamUrl;
|
||||
this.title = this.data.title;
|
||||
}
|
||||
|
||||
showCopyNotification() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CommonModule, KeyValuePipe } from '@angular/common';
|
||||
import { DatePipe, KeyValuePipe, TitleCasePipe } from '@angular/common';
|
||||
import { Component, computed, inject, Optional } from '@angular/core';
|
||||
import { MatButtonModule, MatIconButton } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||
import { MatCard } from '@angular/material/card';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { XtreamStore } from '../xtream.store';
|
||||
@@ -11,12 +11,14 @@ import { XtreamStore } from '../xtream.store';
|
||||
@Component({
|
||||
selector: 'app-recently-viewed',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatCardModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
DatePipe,
|
||||
KeyValuePipe,
|
||||
MatButton,
|
||||
MatCard,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatIconButton,
|
||||
TitleCasePipe,
|
||||
],
|
||||
templateUrl: './recently-viewed.component.html',
|
||||
styleUrl: './recently-viewed.component.scss',
|
||||
|
||||
@@ -21,37 +21,35 @@
|
||||
(error)="
|
||||
$event.target.src = './assets/images/default-poster.png'
|
||||
"
|
||||
alt="Movie poster"
|
||||
/>
|
||||
} @else {
|
||||
<img src="./assets/images/default-poster.png" />
|
||||
<img
|
||||
src="./assets/images/default-poster.png"
|
||||
alt="Default poster"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div class="details">
|
||||
<h2>{{ item?.info?.name || item?.movie_data?.name }}</h2>
|
||||
@if (item?.info?.description || item?.info?.plot) {
|
||||
<label>
|
||||
<div>
|
||||
{{ item?.info?.description || item?.info?.plot }}
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
@if (item?.info?.releasedate) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.RELEASE_DATE' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.releasedate }}
|
||||
</label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.RELEASE_DATE' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.releasedate }}
|
||||
}
|
||||
@if (item?.info?.genre) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div>
|
||||
{{ item?.info?.genre }}
|
||||
</label>
|
||||
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div>
|
||||
{{ item?.info?.genre }}
|
||||
}
|
||||
@if (item?.info?.country) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.COUNTRY' | translate }}:</div>
|
||||
{{ item?.info?.country }}
|
||||
</label>
|
||||
<div class="label">{{ 'XTREAM.COUNTRY' | translate }}:</div>
|
||||
{{ item?.info?.country }}
|
||||
}
|
||||
@if (item?.info?.actors || item?.info?.cast) {
|
||||
<div>
|
||||
@@ -60,36 +58,22 @@
|
||||
</div>
|
||||
}
|
||||
@if (item?.info?.director) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.DIRECTOR' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.director }}
|
||||
</label>
|
||||
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div>
|
||||
{{ item?.info?.director }}
|
||||
}
|
||||
@if (item?.info?.duration) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.DURATION' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.duration }}
|
||||
</label>
|
||||
<div class="label">{{ 'XTREAM.DURATION' | translate }}:</div>
|
||||
{{ item?.info?.duration }}
|
||||
}
|
||||
@if (item?.info?.rating_imdb) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.IMDB_RATING' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.rating_imdb }}
|
||||
</label>
|
||||
<div class="label">{{ 'XTREAM.IMDB_RATING' | translate }}:</div>
|
||||
{{ item?.info?.rating_imdb }}
|
||||
}
|
||||
@if (item?.info?.rating_kinopoisk) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.KINOPOISK_RATING' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.rating_kinopoisk }}
|
||||
</label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.KINOPOISK_RATING' | translate }}:
|
||||
</div>
|
||||
{{ item?.info?.rating_kinopoisk }}
|
||||
}
|
||||
<div class="action-buttons">
|
||||
<button mat-flat-button color="accent" (click)="playVod(item)">
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// should represent the content of a card element - xtream category or VOD or any item from stalker, vportal etc
|
||||
export interface PortalCardItem {
|
||||
// TODO
|
||||
id: string;
|
||||
type: 'category' | 'vod' | 'serial' | 'live'; // TODO: enum
|
||||
name: string;
|
||||
coverUrl?: string;
|
||||
streamType?: 'live' | 'movie'; // TODO: maybe not needed, since type is there
|
||||
categoryId?: string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<div
|
||||
class="container"
|
||||
class="container"
|
||||
[style.background]="
|
||||
item?.info?.backdrop_path && item?.info?.backdrop_path.length > 0
|
||||
? 'linear-gradient(to top, rgba(29,29,29,1) 0%, rgba(0,0,0,0.5) 100%), url(' +
|
||||
@@ -7,122 +7,109 @@
|
||||
') repeat-x'
|
||||
: 'none'
|
||||
"
|
||||
>
|
||||
<div class="image">
|
||||
@if (item.info?.movie_image) {
|
||||
<img
|
||||
[src]="item.info?.movie_image"
|
||||
>
|
||||
<div class="image">
|
||||
@if (item.info?.movie_image) {
|
||||
<img
|
||||
[src]="item.info?.movie_image"
|
||||
(error)="
|
||||
$event.target.src = './assets/images/default-poster.png'
|
||||
"
|
||||
/>
|
||||
} @else {
|
||||
<div class="placeholder-cover"></div>
|
||||
}
|
||||
</div>
|
||||
<div class="details">
|
||||
<h2>{{ item.info.name }}</h2>
|
||||
@if (item.info.description) {
|
||||
<label>
|
||||
{{ item.info.description }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.releasedate) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.RELEASE_DATE' | translate }}:</div>
|
||||
{{ item.info.releasedate }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.genre) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div>
|
||||
{{ item.info.genre }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.country) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.COUNTRY' | translate }}:</div>
|
||||
{{ item.info.country }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.actors) {
|
||||
<div>
|
||||
<div class="label">{{ 'XTREAM.ACTORS' | translate }}:</div>
|
||||
{{ item.info.actors }}
|
||||
</div>
|
||||
}
|
||||
@if (item.info.director) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div>
|
||||
{{ item.info.director }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.duration) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.DURATION' | translate }}:</div>
|
||||
{{ item.info.duration }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.rating_imdb) {
|
||||
<label>
|
||||
<div class="label">{{ 'XTREAM.IMDB_RATING' | translate }}:</div>
|
||||
{{ item.info.rating_imdb }}
|
||||
</label>
|
||||
}
|
||||
@if (item.info.rating_kinopoisk) {
|
||||
<label>
|
||||
<div class="label">
|
||||
{{ 'XTREAM.KINOPOISK_RATING' | translate }}:
|
||||
</div>
|
||||
{{ item.info.rating_kinopoisk }}
|
||||
</label>
|
||||
}
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
(click)="playClicked.emit(item)"
|
||||
>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
{{ 'XTREAM.PLAY' | translate }}</button
|
||||
>
|
||||
@if (!isFavorite) {
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="accent"
|
||||
(click)="toggleFavorite()"
|
||||
>
|
||||
<mat-icon>star_outline</mat-icon>
|
||||
{{ 'PORTALS.ADD_TO_FAVORITES' | translate }}
|
||||
</button>
|
||||
alt="Movie poster"
|
||||
/>
|
||||
} @else {
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="accent"
|
||||
(click)="toggleFavorite()"
|
||||
>
|
||||
<mat-icon>star</mat-icon>
|
||||
{{ 'PORTALS.REMOVE_FROM_FAVORITES' | translate }}
|
||||
</button>
|
||||
<div class="placeholder-cover"></div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (item.info.youtube_trailer) {
|
||||
<div class="details">
|
||||
<h2>{{ item.info.name }}</h2>
|
||||
@if (item.info.description) {
|
||||
<div>
|
||||
{{ item.info.description }}
|
||||
</div>
|
||||
}
|
||||
@if (item.info.releasedate) {
|
||||
<div class="label">{{ 'XTREAM.RELEASE_DATE' | translate }}:</div>
|
||||
{{ item.info.releasedate }}
|
||||
}
|
||||
@if (item.info.genre) {
|
||||
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div>
|
||||
{{ item.info.genre }}
|
||||
}
|
||||
@if (item.info.country) {
|
||||
<div class="label">{{ 'XTREAM.COUNTRY' | translate }}:</div>
|
||||
{{ item.info.country }}
|
||||
}
|
||||
@if (item.info.actors) {
|
||||
<div>
|
||||
<div class="label">{{ 'XTREAM.ACTORS' | translate }}:</div>
|
||||
{{ item.info.actors }}
|
||||
</div>
|
||||
}
|
||||
@if (item.info.director) {
|
||||
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div>
|
||||
{{ item.info.director }}
|
||||
}
|
||||
@if (item.info.duration) {
|
||||
<div class="label">{{ 'XTREAM.DURATION' | translate }}:</div>
|
||||
{{ item.info.duration }}
|
||||
}
|
||||
@if (item.info.rating_imdb) {
|
||||
<div class="label">{{ 'XTREAM.IMDB_RATING' | translate }}:</div>
|
||||
{{ item.info.rating_imdb }}
|
||||
}
|
||||
@if (item.info.rating_kinopoisk) {
|
||||
<div class="label">
|
||||
{{ 'XTREAM.KINOPOISK_RATING' | translate }}:
|
||||
</div>
|
||||
{{ item.info.rating_kinopoisk }}
|
||||
}
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
(click)="playClicked.emit(item)"
|
||||
>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
{{ 'XTREAM.PLAY' | translate }}</button
|
||||
>
|
||||
@if (!isFavorite) {
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="accent"
|
||||
(click)="toggleFavorite()"
|
||||
>
|
||||
<mat-icon>star_outline</mat-icon>
|
||||
{{ 'PORTALS.ADD_TO_FAVORITES' | translate }}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="accent"
|
||||
(click)="toggleFavorite()"
|
||||
>
|
||||
<mat-icon>star</mat-icon>
|
||||
{{ 'PORTALS.REMOVE_FROM_FAVORITES' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (item.info.youtube_trailer) {
|
||||
<div class="youtube-trailer">
|
||||
<h3>{{ 'XTREAM.YOUTUBE_TRAILER' | translate }}</h3>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="450"
|
||||
[src]="
|
||||
'https://www.youtube.com/embed/' + item.info?.youtube_trailer | safe
|
||||
"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
<h3>{{ 'XTREAM.YOUTUBE_TRAILER' | translate }}</h3>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="450"
|
||||
[src]="
|
||||
'https://www.youtube.com/embed/' + item.info?.youtube_trailer
|
||||
| safe
|
||||
"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { PlaylistsService } from 'services';
|
||||
import { XtreamVodDetails } from 'shared-interfaces';
|
||||
import { SafePipe } from './safe.pipe';
|
||||
import { SafePipe } from '../../xtream-tauri/vod-details/safe.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vod-details',
|
||||
|
||||
@@ -41,6 +41,10 @@ import {
|
||||
import { LiveStreamLayoutComponent } from 'shared-portals';
|
||||
import { SettingsStore } from '../services/settings-store.service';
|
||||
import { ExternalPlayerInfoDialogComponent } from '../shared/components/external-player-info-dialog/external-player-info-dialog.component';
|
||||
import {
|
||||
PlayerDialogComponent,
|
||||
PlayerDialogData,
|
||||
} from '../xtream-tauri/player-dialog/player-dialog.component';
|
||||
import { PlaylistErrorViewComponent } from '../xtream/playlist-error-view/playlist-error-view.component';
|
||||
import { Breadcrumb, PortalActions } from './breadcrumb.interface';
|
||||
import { CategoryContentViewComponent } from './category-content-view/category-content-view.component';
|
||||
@@ -48,10 +52,6 @@ import { CategoryViewComponent } from './category-view/category-view.component';
|
||||
import { ContentTypeNavigationItem } from './content-type-navigation-item.interface';
|
||||
import { ContentType } from './content-type.enum';
|
||||
import { NavigationBarComponent } from './navigation-bar/navigation-bar.component';
|
||||
import {
|
||||
PlayerDialogComponent,
|
||||
PlayerDialogData,
|
||||
} from './player-dialog/player-dialog.component';
|
||||
import { PortalStore } from './portal.store';
|
||||
import { SerialDetailsComponent } from './serial-details/serial-details.component';
|
||||
import { VodDetailsComponent } from './vod-details/vod-details.component';
|
||||
@@ -395,7 +395,8 @@ export class XtreamMainContainerComponent implements OnInit, OnDestroy {
|
||||
|
||||
playEpisode(episode: XtreamSerieEpisode) {
|
||||
const playlist = this.currentPlaylist() as Playlist;
|
||||
const { serverUrl, username, password, userAgent, referrer, origin } = playlist;
|
||||
const { serverUrl, username, password, userAgent, referrer, origin } =
|
||||
playlist;
|
||||
const player = this.settingsStore.player();
|
||||
const streamUrl = `${serverUrl}/series/${username}/${password}/${episode.id}.${episode.container_extension}`;
|
||||
if (player === VideoPlayer.MPV) {
|
||||
|
||||
Reference in new issue
Block a user