Skip to content

Commit

Permalink
Merge pull request #25 from vassdeniss/video-player
Browse files Browse the repository at this point in the history
Added video player to review pages
  • Loading branch information
vassdeniss authored Nov 8, 2023
2 parents c7c1aca + 85bd87e commit c7e0bcd
Show file tree
Hide file tree
Showing 26 changed files with 477 additions and 122 deletions.
202 changes: 132 additions & 70 deletions client/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@angular/platform-browser": "^16.1.0",
"@angular/platform-browser-dynamic": "^16.1.0",
"@angular/router": "^16.1.0",
"@angular/youtube-player": "^16.2.11",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
"@types/trusted-types": "^2.0.3",
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/auth/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export function getFormValidationErrors(form: FormGroup) {
message = 'is not valid!';
} else if (keyError === 'notSame') {
message = 'must match repeat password!';
} else if (keyError === 'invalidLinks') {
message = ' : Not all links are valid, or are not comma seperated!';
}

result.push({
Expand Down
5 changes: 5 additions & 0 deletions client/src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { SearchComponent } from './search/search.component';
import { NgxEditorModule } from 'ngx-editor';
import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { PlayerComponent } from './player/player.component';
import { YouTubePlayerModule } from '@angular/youtube-player';

@NgModule({
declarations: [
Expand All @@ -20,6 +22,7 @@ import { TranslateModule } from '@ngx-translate/core';
HomeComponent,
FooterComponent,
SearchComponent,
PlayerComponent,
],
imports: [
CommonModule,
Expand All @@ -28,13 +31,15 @@ import { TranslateModule } from '@ngx-translate/core';
NgxEditorModule,
FormsModule,
TranslateModule,
YouTubePlayerModule,
],
exports: [
NavComponent,
PageSpinnerComponent,
FooterComponent,
SearchComponent,
TranslateModule,
PlayerComponent,
],
})
export class CoreModule {}
2 changes: 0 additions & 2 deletions client/src/app/core/home/home.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,4 @@ ul {
.flex-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
Empty file.
7 changes: 7 additions & 0 deletions client/src/app/core/player/player.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div #youTubePlayer>
<youtube-player
[width]="videoWidth"
[height]="videoHeight"
[videoId]="videoId"
></youtube-player>
</div>
21 changes: 21 additions & 0 deletions client/src/app/core/player/player.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PlayerComponent } from './player.component';

describe('PlayerComponent', () => {
let component: PlayerComponent;
let fixture: ComponentFixture<PlayerComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PlayerComponent]
});
fixture = TestBed.createComponent(PlayerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
34 changes: 34 additions & 0 deletions client/src/app/core/player/player.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
Input,
ViewChild,
} from '@angular/core';

@Component({
selector: 'app-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.css'],
})
export class PlayerComponent implements AfterViewInit {
@ViewChild('youTubePlayer') player!: ElementRef<HTMLDivElement>;
@Input('videoId') videoId!: string;

videoHeight: number | undefined;
videoWidth: number | undefined;

constructor(private changeDetectorRef: ChangeDetectorRef) {}

ngAfterViewInit(): void {
this.onResize();
window.addEventListener('resize', this.onResize.bind(this));
}

onResize(): void {
this.videoWidth = Math.min(this.player.nativeElement.clientWidth, 1200);
this.videoHeight = this.videoWidth * 0.6;
this.changeDetectorRef.detectChanges();
}
}
12 changes: 12 additions & 0 deletions client/src/app/review/create-review/create-review.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ <h2>{{ "review.create-review.header" | translate }}</h2>
(change)="onFileChange($event)"
/>
</div>
<div class="form-group">
<label for="setVideoIds">{{
"review.create-review.videos" | translate
}}</label>
<input
type="text"
id="setVideoIds"
name="setVideoIds"
placeholder="Enter youtube URLs..."
formControlName="setVideoIds"
/>
</div>
<div class="image-wrapper" *ngFor="let url of images; let i = index">
<img [src]="url" />
<a class="delete-button" (click)="deleteImage(i)">&times;</a>
Expand Down
69 changes: 56 additions & 13 deletions client/src/app/review/create-review/create-review.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import {
AbstractControl,
FormBuilder,
FormGroup,
Validators,
} from '@angular/forms';
import { PopupService } from 'src/app/services/popup.service';
import { getFormValidationErrors } from '../../auth/helpers';
import { ReviewService } from 'src/app/services/review.service';
Expand All @@ -15,19 +20,25 @@ import { Editor, Toolbar } from 'ngx-editor';
export class CreateReviewComponent implements OnInit, OnDestroy {
errors: string[] = [];
images: string[] = [];
reviewForm = this.fb.group({
content: [
'',
[
Validators.required,
Validators.minLength(50),
Validators.maxLength(5000),
reviewForm = this.fb.group(
{
content: [
'',
[
Validators.required,
Validators.minLength(50),
Validators.maxLength(5000),
],
],
],
images: [''],
setImages: [this.images],
_id: [''],
});
images: [''],
setImages: [this.images],
setVideoIds: [''],
_id: [''],
},
{
validator: this.linkValidator('setVideoIds'),
}
);

editor!: Editor;
toolbar: Toolbar = [
Expand Down Expand Up @@ -112,4 +123,36 @@ export class CreateReviewComponent implements OnInit, OnDestroy {
setImages: this.images,
});
}

linkValidator(controlName: string) {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];

if (control.value === '') {
return;
}

if (control.errors && !control.errors['invalidLinks']) {
return;
}

const links = control.value.split(',').map((link: string) => link.trim());
const isValid = links.every((link: string) => this.isValidUrl(link));

console.log(links);

isValid
? control.setErrors(null)
: control.setErrors({ invalidLinks: true });

return isValid ? null : { invalidLinks: true };
};
}

isValidUrl(url: string) {
const pattern =
/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|live\/|v\/)?)([\w\-]+)(\S+)?$/gm;

return pattern.test(url);
}
}
70 changes: 51 additions & 19 deletions client/src/app/review/detail-review/detail-review.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,6 @@ img {
cursor: pointer;
}

/* Responsive styles for mobile */
@media only screen and (max-width: 600px) {
.set-info,
.set-image {
max-width: 100%;
}

.container {
flex-direction: column;
justify-content: center;
align-items: center;
}

.set-info {
padding: unset;
text-align: center;
}
}

.enlarged-image-container {
display: flex;
justify-content: center;
Expand Down Expand Up @@ -139,3 +120,54 @@ img {
.button-warning:hover {
background-color: rgb(201, 131, 2);
}

li.playlist-item {
padding: 10px;
list-style: none;
background: #e6e6e6;
cursor: pointer;
text-align: center;
margin-bottom: 5px;
}

ul {
padding: 0;
margin: 0 auto;
}

.set-videos {
width: 50%;
background-color: #fff;
margin: 0 auto;
}

/* Responsive styles for mobile */
@media only screen and (max-width: 600px) {
.set-info,
.set-image {
max-width: 100%;
}

.container {
flex-direction: column;
justify-content: center;
align-items: center;
}

.set-info {
padding: unset;
text-align: center;
}

.set-videos {
margin: unset;
width: 100%;
}
}

@media only screen and (max-width: 1100px) {
.set-videos {
margin: unset;
width: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ <h2>{{ "review.detail-review.review" | translate }}</h2>
*ngFor="let image of review?.setImages"
/>
</div>
<div class="set-videos" *ngIf="playlist.length > 0">
<app-player [videoId]="activeVideo" />
<ul>
<li class="playlist-item" (click)="previousVideo()">Previous</li>
<li class="playlist-item" (click)="nextVideo()">Next</li>
</ul>
</div>
<div *ngIf="review!.setMinifigures!.length > 0" class="minifigures">
<h2>{{ "review.detail-review.minifigures" | translate }}</h2>
<div class="minifigure-container">
Expand Down
32 changes: 31 additions & 1 deletion client/src/app/review/detail-review/detail-review.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Editor } from 'ngx-editor';
Expand All @@ -7,6 +7,8 @@ import { ReviewService } from 'src/app/services/review.service';
import { UserService } from 'src/app/services/user.service';
import { Review } from 'src/app/types/reviewType';

let apiLoaded = false;

@Component({
selector: 'app-detail-review',
templateUrl: './detail-review.component.html',
Expand All @@ -19,6 +21,9 @@ export class DetailReviewComponent implements OnInit, OnDestroy {
isOwner: boolean = false;
customPopupContent: string | undefined = undefined;
editor!: Editor;
playlist: any[] = [];
currentIndex: number = 0;
activeVideo: any;

constructor(
private reviewService: ReviewService,
Expand All @@ -30,8 +35,17 @@ export class DetailReviewComponent implements OnInit, OnDestroy {
) {}

ngOnInit(): void {
if (!apiLoaded) {
const tag = document.createElement('script');
tag.src = `http://www.youtube.com/iframe_api`;
document.body.appendChild(tag);
apiLoaded = true;
}

this.route.data.subscribe(({ review }) => {
this.review = review;
this.playlist = this.review!.setVideoIds!;
this.activeVideo = this.playlist[this.currentIndex];
});
this.editor = new Editor();
this.isOwner = this.userService.user?._id === this.review?.userId;
Expand All @@ -41,6 +55,22 @@ export class DetailReviewComponent implements OnInit, OnDestroy {
this.editor.destroy();
}

previousVideo() {
this.currentIndex--;
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
this.activeVideo = this.playlist[this.currentIndex];
}

nextVideo() {
this.currentIndex++;
if (this.currentIndex === this.playlist.length) {
this.currentIndex--;
}
this.activeVideo = this.playlist[this.currentIndex];
}

enlargeImage(image?: string): void {
this.popup.show();
this.enlargeImageSource = image || '';
Expand Down
Loading

0 comments on commit c7e0bcd

Please sign in to comment.