refactor: extract MoviesListComponent to simplify CategoriesComponent

This commit is contained in:
Chneemann 2025-04-20 08:04:45 +02:00
parent 59d5b842b6
commit 28acea1663
13 changed files with 269 additions and 192 deletions

View file

@ -11,9 +11,9 @@
<app-hero-banner <app-hero-banner
*ngIf=" *ngIf="
currentMovie.length === 1 || currentMovie.length === 1 ||
(this.checkScreenWidth() && currentMovie.length > 1) (this.isWideScreen() && currentMovie.length > 1)
" "
[screenWidth]="checkScreenWidth()" [isWideScreen]="isWideScreen()"
[currentMovie]="currentMovie" [currentMovie]="currentMovie"
[favoriteMovies]="favoriteMovies" [favoriteMovies]="favoriteMovies"
[watchedMovies]="watchedMovies" [watchedMovies]="watchedMovies"
@ -24,12 +24,11 @@
(favoriteMovieChange)="onFavoriteMovieChange($event)" (favoriteMovieChange)="onFavoriteMovieChange($event)"
></app-hero-banner> ></app-hero-banner>
<!-- Spacer --> <!-- Spacer -->
<div *ngIf="!this.checkScreenWidth()" class="spacer"></div> <div *ngIf="!this.isWideScreen()" class="spacer"></div>
<!-- Movie Categories --> <!-- Movie Categories -->
<app-categories <app-categories
*ngIf=" *ngIf="
this.checkScreenWidth() || this.isWideScreen() || (!this.isWideScreen() && currentMovie.length === 0)
(!this.checkScreenWidth() && currentMovie.length === 0)
" "
[movies]="movies" [movies]="movies"
[currentMovie]="currentMovie[0]?.id" [currentMovie]="currentMovie[0]?.id"

View file

@ -1,8 +1,8 @@
@use "./../../../../assets/style/colors.scss" as *; @use "./../../../../assets/style/colors.scss" as *;
section { section {
height: 100vh; height: 100dvh;
width: 100vw; width: 100dvw;
background-color: $black; background-color: $black;
overflow: hidden; overflow: hidden;
} }

View file

@ -32,7 +32,6 @@ export class BrowseComponent implements OnInit {
currentMovie: any[] = []; currentMovie: any[] = [];
playMovie: string = ''; playMovie: string = '';
isLoading: boolean = true; isLoading: boolean = true;
isWideScreen: boolean = false;
uploadMovieOverview: boolean = false; uploadMovieOverview: boolean = false;
currentResolution: '360p' | '720p' | '1080p' = '720p'; currentResolution: '360p' | '720p' | '1080p' = '720p';
movieIsUploaded: { [resolution: string]: boolean } = { movieIsUploaded: { [resolution: string]: boolean } = {
@ -49,7 +48,7 @@ export class BrowseComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.loadLikedAndWatchedMovies(); this.loadLikedAndWatchedMovies();
await this.loadAllMovies(); await this.loadAllMovies();
if (this.checkScreenWidth()) { if (this.isWideScreen()) {
this.currentMovie.length === 0 ? this.loadRandomMovie() : null; this.currentMovie.length === 0 ? this.loadRandomMovie() : null;
} }
} }
@ -79,15 +78,15 @@ export class BrowseComponent implements OnInit {
} }
onMoviesChange(updatedMovies: any[]) { onMoviesChange(updatedMovies: any[]) {
if (this.checkScreenWidth()) { if (this.isWideScreen()) {
this.loadRandomMovie(); this.loadRandomMovie();
} else { } else {
this.currentMovie = updatedMovies; this.currentMovie = updatedMovies;
} }
} }
checkScreenWidth() { isWideScreen() {
return (this.isWideScreen = window.innerWidth > 600); return window.innerWidth > 600;
} }
onMovieIsUploadedChange(newStatus: { [resolution: string]: boolean }) { onMovieIsUploadedChange(newStatus: { [resolution: string]: boolean }) {

View file

@ -1,82 +1,44 @@
<section *ngIf="movies.length > 0" class="hide-scrollbar"> @if (movies.length > 0) {
<!-- New movies added this week (starts on Mondays) --> <section class="hide-scrollbar">
<div *ngIf="recentMovies().length > 0" class="category"> <!-- New movies this week -->
@if (recentMovies().length > 0) {
<div class="category">
<p>New movies added this week</p> <p>New movies added this week</p>
<div class="movies"> <app-movie-list
<!-- Load movies --> [movies]="recentMovies()"
@for (movie of recentMovies(); track movie) { [currentMovie]="currentMovie"
<div class="movie" (click)="openCurrentMovie(movie.id)"> [watchedMovies]="watchedMovies"
<img (currentMovieId)="openCurrentMovie($event)"
[ngClass]="{ selected: movie.id === currentMovie }" ></app-movie-list>
[src]="getThumbnailUrl(movie.id, movie.file_name)"
alt=""
/>
</div> </div>
} }
<!-- Buttons for scrolling -->
<div class="scroll-buttons">
<button class="scroll-left" (click)="scrollLeft($event)"></button>
<button class="scroll-right" (click)="scrollRight($event)"></button>
</div>
</div>
</div>
<!-- Column for favorites --> <!-- Favorites -->
<div *ngIf="favoriteMovies.length > 0" class="category"> @if (favoriteMovies.length > 0) {
<div class="category">
<p>Favorites</p> <p>Favorites</p>
<div class="movies"> <app-movie-list
<!-- Load movies --> [movies]="getFavoriteMovies()"
@for (favorite of favoriteMovies; track favorite) { [currentMovie]="currentMovie"
<!-- Filter movies --> [watchedMovies]="watchedMovies"
@for (movie of movies; track movie) { (currentMovieId)="openCurrentMovie($event)"
<!-- If movie favorite --> ></app-movie-list>
@if (movie.id === favorite) {
<div class="movie" (click)="openCurrentMovie(movie.id)">
<img
[ngClass]="{ selected: movie.id === currentMovie }"
[src]="getThumbnailUrl(movie.id, movie.file_name)"
alt=""
/>
</div>
} } }
<!-- Buttons for scrolling -->
<div class="scroll-buttons">
<button class="scroll-left" (click)="scrollLeft($event)"></button>
<button class="scroll-right" (click)="scrollRight($event)"></button>
</div>
</div>
</div> </div>
}
<!-- Genre columns --> <!-- Genre columns -->
@for (filmGenre of filmGenres; track filmGenre) { @for (filmGenre of filmGenres; track filmGenre) {
<!-- Movie available in genre --> <!-- Movies by genre -->
@if (allMovies(filmGenre.code).length > 0) { @if (allMovies(filmGenre.code).length > 0) {
<div class="category"> <div class="category">
<p>{{ filmGenre.name }}</p> <p>{{ filmGenre.name }}</p>
<div class="movies"> <app-movie-list
<!-- Load movies --> [movies]="allMovies(filmGenre.code)"
@for (movie of allMovies(filmGenre.code); track movie) { [currentMovie]="currentMovie"
[watchedMovies]="watchedMovies"
<div class="movie" (click)="openCurrentMovie(movie.id)"> (currentMovieId)="openCurrentMovie($event)"
<div class="banner"> ></app-movie-list>
<img
[ngClass]="{ selected: movie.id === currentMovie }"
[src]="getThumbnailUrl(movie.id, movie.file_name)"
alt=""
/>
</div>
@if (watchedMovies.includes(movie.id)) {
<div class="watched">
<img src="./../../../../../assets/img/open-eye.svg" alt="" />
</div>
}
</div>
}
<!-- Buttons for scrolling -->
<div class="scroll-buttons">
<button class="scroll-left" (click)="scrollLeft($event)"></button>
<button class="scroll-right" (click)="scrollRight($event)"></button>
</div>
</div>
</div> </div>
} } } }
</section> </section>
}

View file

@ -16,103 +16,6 @@ section {
} }
} }
// Movies
.movie {
position: relative;
width: 213px;
height: 120px;
padding: 6px;
.banner {
img {
width: 213px;
height: 120px;
transition: scale 300ms ease-in-out;
border: 2px solid rgba($black, 0);
cursor: pointer;
&:hover {
scale: 1.02;
border: 2px solid $blue;
}
}
}
.watched {
position: absolute;
top: 15px;
right: 15px;
z-index: 2;
img {
width: 24px;
height: auto;
filter: brightness(0) saturate(100%) invert(100%) sepia(28%) saturate(1%)
hue-rotate(288deg) brightness(101%) contrast(101%)
drop-shadow(0 0 5px rgba(38, 143, 255, 1));
}
}
}
.selected {
border: 2px solid $blue !important;
}
.movies {
display: flex;
overflow-x: auto;
overflow-y: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
padding: 0 24x;
}
.movies::-webkit-scrollbar {
display: none;
}
// Scroll buttons
.scroll-buttons {
position: absolute;
top: 50%;
width: 100%;
display: none;
justify-content: space-between;
pointer-events: none;
transform: translateY(-50%);
}
.scroll-buttons.show {
display: flex;
}
.scroll-left,
.scroll-right {
background: url("./../../../../../assets/img/arrow_forward.svg") no-repeat
center;
background-size: 20px 30px;
border: none;
width: 40px;
height: 40px;
cursor: pointer;
pointer-events: auto;
&:hover {
transition: 125ms ease-in-out;
filter: brightness(0) saturate(100%) invert(26%) sepia(77%) saturate(2342%)
hue-rotate(228deg) brightness(83%) contrast(115%);
}
}
.scroll-left {
position: absolute;
left: -32px;
background-image: url("./../../../../../assets/img/arrow_back.svg");
}
.scroll-right {
position: absolute;
right: -38px;
background-image: url("./../../../../../assets/img/arrow_forward.svg");
}
// Hide scrollbar // Hide scrollbar
.hide-scrollbar { .hide-scrollbar {
@ -130,12 +33,6 @@ section {
/*------------- RESPONSIVE -------------*/ /*------------- RESPONSIVE -------------*/
@media screen and (max-width: 600px) {
.hide-scrollbar {
height: calc(100% - 120px);
}
}
@media screen and (max-width: 400px) { @media screen and (max-width: 400px) {
.category { .category {
p { p {
@ -144,3 +41,13 @@ section {
} }
} }
} }
@media screen and (max-width: 600px) {
section {
margin: 0 12px;
}
.hide-scrollbar {
height: calc(100% - 120px);
}
}

View file

@ -8,11 +8,12 @@ import {
Output, Output,
} from '@angular/core'; } from '@angular/core';
import { environment } from '../../../../../environments/environment'; import { environment } from '../../../../../environments/environment';
import { MoviesListComponent } from './movie-list/movie-list.component';
@Component({ @Component({
selector: 'app-categories', selector: 'app-categories',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule, MoviesListComponent],
templateUrl: './categories.component.html', templateUrl: './categories.component.html',
styleUrl: './categories.component.scss', styleUrl: './categories.component.scss',
}) })
@ -47,6 +48,12 @@ export class CategoriesComponent implements AfterViewInit {
{ code: 'western', name: 'Western' }, { code: 'western', name: 'Western' },
]; ];
getFavoriteMovies() {
return this.movies.filter((movie) =>
this.favoriteMovies.includes(movie.id)
);
}
ngAfterViewInit() { ngAfterViewInit() {
this.checkScroll(); this.checkScroll();
} }

View file

@ -0,0 +1,33 @@
<div class="movies">
<!-- List of movies -->
@for (movie of movies; track movie) {
<div class="movie" (click)="openCurrentMovie(movie.id)">
<div class="banner">
<img
[ngClass]="{ selected: movie.id === currentMovie }"
[src]="getThumbnailUrl(movie.id, movie.file_name)"
alt=""
/>
</div>
<div *ngIf="watchedMovies.includes(movie.id)" class="watched">
<img src="./../../../../../assets/img/open-eye.svg" alt="" />
</div>
</div>
}
<!-- Scroll buttons -->
<div class="scroll-buttons">
<img
class="scroll-left"
(click)="scrollLeft($event)"
src="./../../../../../assets/img/arrow_back.svg"
alt=""
/>
<img
class="scroll-right"
(click)="scrollRight($event)"
src="./../../../../../assets/img/arrow_forward.svg"
alt=""
/>
</div>
</div>

View file

@ -0,0 +1,99 @@
@use "./../../../../../../assets/style/colors.scss" as *;
.movies {
display: flex;
overflow-x: auto;
overflow-y: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
padding-bottom: 24px;
}
.movies::-webkit-scrollbar {
display: none;
}
.movie {
position: relative;
width: 213px;
height: 120px;
padding: 6px;
.banner {
img {
width: 213px;
height: 120px;
transition: scale 300ms ease-in-out;
border: 2px solid rgba($black, 0);
cursor: pointer;
&:hover {
scale: 1.02;
border: 2px solid $blue;
}
}
}
.watched {
position: absolute;
top: 15px;
right: 15px;
z-index: 2;
img {
width: 24px;
height: auto;
filter: brightness(0) saturate(100%) invert(100%) sepia(28%) saturate(1%)
hue-rotate(288deg) brightness(101%) contrast(101%)
drop-shadow(0 0 5px rgba(38, 143, 255, 1));
}
}
}
.selected {
border: 2px solid $blue !important;
}
// Scroll buttons
.scroll-buttons {
position: absolute;
top: 50%;
width: 100%;
display: none;
justify-content: space-between;
pointer-events: none;
transform: translateY(-50%);
}
.scroll-buttons.show {
display: flex;
}
.scroll-left,
.scroll-right {
position: absolute;
background-size: 20px 30px;
border: none;
width: 40px;
height: 40px;
cursor: pointer;
pointer-events: auto;
&:hover {
transition: 125ms ease-in-out;
filter: brightness(0) saturate(100%) invert(26%) sepia(77%) saturate(2342%)
hue-rotate(228deg) brightness(83%) contrast(115%);
}
}
.scroll-left {
left: -25px;
}
.scroll-right {
right: -25px;
}
/*------------- RESPONSIVE -------------*/
@media screen and (max-width: 600px) {
.hide-scrollbar {
height: calc(100% - 120px);
}
}

View file

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MoviesListComponent } from './movie-list.component';
describe('MoviesListComponent', () => {
let component: MoviesListComponent;
let fixture: ComponentFixture<MoviesListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MoviesListComponent],
}).compileComponents();
fixture = TestBed.createComponent(MoviesListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,47 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { environment } from '../../../../../../environments/environment';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-movie-list',
standalone: true,
imports: [CommonModule],
templateUrl: './movie-list.component.html',
styleUrls: ['./movie-list.component.scss'],
})
export class MoviesListComponent {
@Input() movies: any[] = [];
@Input() currentMovie: number = 0;
@Input() watchedMovies: any[] = [];
@Input() movieCategory: string = '';
@Output() currentMovieId = new EventEmitter<number>();
getThumbnailUrl(movieId: number, fileName: string): string {
return `${environment.baseUrl}/media/thumbnails/${movieId}/${fileName}_480p.jpg`;
}
openCurrentMovie(movieId: number) {
this.currentMovie = movieId;
this.currentMovieId.emit(movieId);
}
scrollLeft(event: MouseEvent) {
const button = event.target as HTMLElement;
const container = button
.closest('.category')
?.querySelector('.movies') as HTMLElement;
if (container) {
container.scrollLeft -= 217;
}
}
scrollRight(event: MouseEvent) {
const button = event.target as HTMLElement;
const container = button
.closest('.category')
?.querySelector('.movies') as HTMLElement;
if (container) {
container.scrollLeft += 217;
}
}
}

View file

@ -1,12 +1,12 @@
<section <section
[ngClass]="{ [ngClass]="{
fullVh: !screenWidth fullVh: !isWideScreen
}" }"
> >
<div <div
class="movie-banner" class="movie-banner"
[ngClass]="{ [ngClass]="{
fullVhBanner: !screenWidth fullVhBanner: !isWideScreen
}" }"
> >
<!-- Fallback image is displayed before the video is loaded --> <!-- Fallback image is displayed before the video is loaded -->
@ -30,21 +30,21 @@
<div <div
class="content" class="content"
[ngClass]="{ [ngClass]="{
fullVhContent: !screenWidth fullVhContent: !isWideScreen
}" }"
> >
<div class="title">{{ currentMovie[0].title }}</div> <div class="title">{{ currentMovie[0].title }}</div>
<div <div
class="description hide-scrollbar" class="description hide-scrollbar"
[ngClass]="{ [ngClass]="{
fullVhDescription: !screenWidth fullVhDescription: !isWideScreen
}" }"
> >
{{ currentMovie[0].description }} {{ currentMovie[0].description }}
</div> </div>
<div class="buttons"> <div class="buttons">
<app-btn-small <app-btn-small
*ngIf="!screenWidth" *ngIf="!isWideScreen"
[imgPath]="'back'" [imgPath]="'back'"
[imgRadius]="'24px'" [imgRadius]="'24px'"
(click)="backToCategory([])" (click)="backToCategory([])"

View file

@ -25,7 +25,7 @@ import { UserService } from '../../../../services/user.service';
export class HeroBannerComponent implements OnChanges { export class HeroBannerComponent implements OnChanges {
@ViewChild('videoElement') videoElementRef!: ElementRef<HTMLVideoElement>; @ViewChild('videoElement') videoElementRef!: ElementRef<HTMLVideoElement>;
@Input() currentMovie: any[] = []; @Input() currentMovie: any[] = [];
@Input() screenWidth: boolean = false; @Input() isWideScreen: boolean = false;
@Input() favoriteMovies: any[] = []; @Input() favoriteMovies: any[] = [];
@Input() watchedMovies: any[] = []; @Input() watchedMovies: any[] = [];
@Output() playMovie = new EventEmitter<string>(); @Output() playMovie = new EventEmitter<string>();

View file

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m321-80-71-71 329-329-329-329 71-71 400 400L321-80Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed" style="transform: rotate(180deg)">
<path d="M400-80 0-480l400-400 71 71-329 329 329 329-71 71Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 213 B