diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-10-09 14:28:44 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-10-09 14:28:44 +0200 |
commit | a685e25ca05f08ad1b3f7fbaccc8744727bd8d27 (patch) | |
tree | e50fbc2f260a0017113c4668c3c0f3d2fd76ab87 /client/src/app/videos/video-watch | |
parent | 2ed6a0aedc2d2f6b1ac2fd9a1ac137772831f713 (diff) | |
download | PeerTube-a685e25ca05f08ad1b3f7fbaccc8744727bd8d27.tar.gz PeerTube-a685e25ca05f08ad1b3f7fbaccc8744727bd8d27.tar.zst PeerTube-a685e25ca05f08ad1b3f7fbaccc8744727bd8d27.zip |
Try to optimize frontend
Diffstat (limited to 'client/src/app/videos/video-watch')
10 files changed, 0 insertions, 957 deletions
diff --git a/client/src/app/videos/video-watch/index.ts b/client/src/app/videos/video-watch/index.ts deleted file mode 100644 index 105872469..000000000 --- a/client/src/app/videos/video-watch/index.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export * from './video-magnet.component' | ||
2 | export * from './video-share.component' | ||
3 | export * from './video-report.component' | ||
4 | export * from './video-watch.component' | ||
diff --git a/client/src/app/videos/video-watch/video-magnet.component.html b/client/src/app/videos/video-watch/video-magnet.component.html deleted file mode 100644 index 484280c45..000000000 --- a/client/src/app/videos/video-watch/video-magnet.component.html +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | <div bsModal #modal="bs-modal" class="modal" tabindex="-1"> | ||
2 | <div class="modal-dialog"> | ||
3 | <div class="modal-content modal-lg"> | ||
4 | |||
5 | <div class="modal-header"> | ||
6 | <button type="button" class="close" aria-label="Close" (click)="hide()"> | ||
7 | <span aria-hidden="true">×</span> | ||
8 | </button> | ||
9 | <h4 class="modal-title">Magnet Uri</h4> | ||
10 | </div> | ||
11 | |||
12 | <div class="modal-body"> | ||
13 | <div *ngFor="let file of video.files"> | ||
14 | <label>{{ file.resolutionLabel }}</label> | ||
15 | <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" /> | ||
16 | </div> | ||
17 | </div> | ||
18 | </div> | ||
19 | </div> | ||
20 | </div> | ||
diff --git a/client/src/app/videos/video-watch/video-magnet.component.ts b/client/src/app/videos/video-watch/video-magnet.component.ts deleted file mode 100644 index f9432e92c..000000000 --- a/client/src/app/videos/video-watch/video-magnet.component.ts +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | ||
2 | |||
3 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
4 | |||
5 | import { Video } from '../shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-video-magnet', | ||
9 | templateUrl: './video-magnet.component.html' | ||
10 | }) | ||
11 | export class VideoMagnetComponent { | ||
12 | @Input() video: Video = null | ||
13 | |||
14 | @ViewChild('modal') modal: ModalDirective | ||
15 | |||
16 | constructor () { | ||
17 | // empty | ||
18 | } | ||
19 | |||
20 | show () { | ||
21 | this.modal.show() | ||
22 | } | ||
23 | |||
24 | hide () { | ||
25 | this.modal.hide() | ||
26 | } | ||
27 | } | ||
diff --git a/client/src/app/videos/video-watch/video-report.component.html b/client/src/app/videos/video-watch/video-report.component.html deleted file mode 100644 index 741080ead..000000000 --- a/client/src/app/videos/video-watch/video-report.component.html +++ /dev/null | |||
@@ -1,38 +0,0 @@ | |||
1 | <div bsModal #modal="bs-modal" class="modal" tabindex="-1"> | ||
2 | <div class="modal-dialog"> | ||
3 | <div class="modal-content modal-lg"> | ||
4 | |||
5 | <div class="modal-header"> | ||
6 | <button type="button" class="close" aria-label="Close" (click)="hide()"> | ||
7 | <span aria-hidden="true">×</span> | ||
8 | </button> | ||
9 | <h4 class="modal-title">Report video</h4> | ||
10 | </div> | ||
11 | |||
12 | <div class="modal-body"> | ||
13 | |||
14 | <form novalidate [formGroup]="form"> | ||
15 | <div class="form-group"> | ||
16 | <label for="description">Reason</label> | ||
17 | <textarea | ||
18 | id="reason" class="form-control" placeholder="Reason..." | ||
19 | formControlName="reason" | ||
20 | > | ||
21 | </textarea> | ||
22 | <div *ngIf="formErrors.reason" class="alert alert-danger"> | ||
23 | {{ formErrors.reason }} | ||
24 | </div> | ||
25 | </div> | ||
26 | |||
27 | <div class="form-group"> | ||
28 | <input | ||
29 | type="button" value="Report" class="btn btn-default form-control" | ||
30 | [disabled]="!form.valid" (click)="report()" | ||
31 | > | ||
32 | </div> | ||
33 | </form> | ||
34 | |||
35 | </div> | ||
36 | </div> | ||
37 | </div> | ||
38 | </div> | ||
diff --git a/client/src/app/videos/video-watch/video-report.component.ts b/client/src/app/videos/video-watch/video-report.component.ts deleted file mode 100644 index d9c83a640..000000000 --- a/client/src/app/videos/video-watch/video-report.component.ts +++ /dev/null | |||
@@ -1,69 +0,0 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms' | ||
3 | |||
4 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | |||
7 | import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared' | ||
8 | import { Video, VideoService } from '../shared' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-video-report', | ||
12 | templateUrl: './video-report.component.html' | ||
13 | }) | ||
14 | export class VideoReportComponent extends FormReactive implements OnInit { | ||
15 | @Input() video: Video = null | ||
16 | |||
17 | @ViewChild('modal') modal: ModalDirective | ||
18 | |||
19 | error: string = null | ||
20 | form: FormGroup | ||
21 | formErrors = { | ||
22 | reason: '' | ||
23 | } | ||
24 | validationMessages = { | ||
25 | reason: VIDEO_ABUSE_REASON.MESSAGES | ||
26 | } | ||
27 | |||
28 | constructor ( | ||
29 | private formBuilder: FormBuilder, | ||
30 | private videoAbuseService: VideoAbuseService, | ||
31 | private notificationsService: NotificationsService | ||
32 | ) { | ||
33 | super() | ||
34 | } | ||
35 | |||
36 | ngOnInit () { | ||
37 | this.buildForm() | ||
38 | } | ||
39 | |||
40 | buildForm () { | ||
41 | this.form = this.formBuilder.group({ | ||
42 | reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ] | ||
43 | }) | ||
44 | |||
45 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | ||
46 | } | ||
47 | |||
48 | show () { | ||
49 | this.modal.show() | ||
50 | } | ||
51 | |||
52 | hide () { | ||
53 | this.modal.hide() | ||
54 | } | ||
55 | |||
56 | report () { | ||
57 | const reason = this.form.value['reason'] | ||
58 | |||
59 | this.videoAbuseService.reportVideo(this.video.id, reason) | ||
60 | .subscribe( | ||
61 | () => { | ||
62 | this.notificationsService.success('Success', 'Video reported.') | ||
63 | this.hide() | ||
64 | }, | ||
65 | |||
66 | err => this.notificationsService.error('Error', err.message) | ||
67 | ) | ||
68 | } | ||
69 | } | ||
diff --git a/client/src/app/videos/video-watch/video-share.component.html b/client/src/app/videos/video-watch/video-share.component.html deleted file mode 100644 index 88f59c063..000000000 --- a/client/src/app/videos/video-watch/video-share.component.html +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | <div bsModal #modal="bs-modal" class="modal" tabindex="-1"> | ||
2 | <div class="modal-dialog modal-lg"> | ||
3 | <div class="modal-content"> | ||
4 | |||
5 | <div class="modal-header"> | ||
6 | <button type="button" class="close" aria-label="Close" (click)="hide()"> | ||
7 | <span aria-hidden="true">×</span> | ||
8 | </button> | ||
9 | <h4 class="modal-title">Share</h4> | ||
10 | </div> | ||
11 | |||
12 | <div class="modal-body"> | ||
13 | <div class="form-group"> | ||
14 | <label>URL</label> | ||
15 | <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" /> | ||
16 | </div> | ||
17 | |||
18 | <div class="form-group"> | ||
19 | <label>Embed</label> | ||
20 | <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" /> | ||
21 | </div> | ||
22 | |||
23 | <div *ngIf="notSecure()" class="alert alert-warning"> | ||
24 | The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). | ||
25 | </div> | ||
26 | </div> | ||
27 | </div> | ||
28 | </div> | ||
29 | </div> | ||
diff --git a/client/src/app/videos/video-watch/video-share.component.ts b/client/src/app/videos/video-watch/video-share.component.ts deleted file mode 100644 index 133f93498..000000000 --- a/client/src/app/videos/video-watch/video-share.component.ts +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | ||
2 | |||
3 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
4 | |||
5 | import { Video } from '../shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-video-share', | ||
9 | templateUrl: './video-share.component.html' | ||
10 | }) | ||
11 | export class VideoShareComponent { | ||
12 | @Input() video: Video = null | ||
13 | |||
14 | @ViewChild('modal') modal: ModalDirective | ||
15 | |||
16 | constructor () { | ||
17 | // empty | ||
18 | } | ||
19 | |||
20 | show () { | ||
21 | this.modal.show() | ||
22 | } | ||
23 | |||
24 | hide () { | ||
25 | this.modal.hide() | ||
26 | } | ||
27 | |||
28 | getVideoIframeCode () { | ||
29 | return '<iframe width="560" height="315" ' + | ||
30 | 'src="' + window.location.origin + '/videos/embed/' + this.video.uuid + '" ' + | ||
31 | 'frameborder="0" allowfullscreen>' + | ||
32 | '</iframe>' | ||
33 | } | ||
34 | |||
35 | getVideoUrl () { | ||
36 | return window.location.href | ||
37 | } | ||
38 | |||
39 | notSecure () { | ||
40 | return window.location.protocol === 'http:' | ||
41 | } | ||
42 | } | ||
diff --git a/client/src/app/videos/video-watch/video-watch.component.html b/client/src/app/videos/video-watch/video-watch.component.html deleted file mode 100644 index 88863131a..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.html +++ /dev/null | |||
@@ -1,184 +0,0 @@ | |||
1 | <div *ngIf="error" class="row"> | ||
2 | <div class="alert alert-danger"> | ||
3 | The video load seems to be abnormally long. | ||
4 | <ul> | ||
5 | <li>Maybe the server {{ video.podHost }} is down :(</li> | ||
6 | <li> | ||
7 | If not, you can report an issue on | ||
8 | <a href="https://github.com/Chocobozzz/PeerTube/issues" title="Report an issue"> | ||
9 | https://github.com/Chocobozzz/PeerTube/issues | ||
10 | </a> | ||
11 | </li> | ||
12 | </ul> | ||
13 | </div> | ||
14 | </div> | ||
15 | |||
16 | <div class="row"> | ||
17 | <!-- We need the video container for videojs so we just hide it --> | ||
18 | <div [hidden]="videoNotFound" class="embed-responsive embed-responsive-19by9"> | ||
19 | <video id="video-container" class="video-js vjs-sublime-skin"></video> | ||
20 | </div> | ||
21 | |||
22 | <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div> | ||
23 | </div> | ||
24 | |||
25 | <!-- P2P informations --> | ||
26 | <div id="torrent-info" class="row"> | ||
27 | <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div> | ||
28 | <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div> | ||
29 | <div id="torrent-info-peers" class="col-md-4 col-sm-4 col-xs-4">Number of peers: {{ numPeers }}</div> | ||
30 | </div> | ||
31 | |||
32 | <!-- Video informations --> | ||
33 | <div *ngIf="video !== null" id="video-info"> | ||
34 | <div class="row video-name-views"> | ||
35 | <div class="col-xs-8 col-md-8 video-name"> | ||
36 | {{ video.name }} | ||
37 | </div> | ||
38 | |||
39 | <div class="col-xs-4 col-md-4 pull-right video-views"> | ||
40 | {{ video.views}} views | ||
41 | </div> | ||
42 | </div> | ||
43 | |||
44 | <div class="row video-small-blocks"> | ||
45 | <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-author"> | ||
46 | <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'author', search: video.author }]"> | ||
47 | <span class="glyphicon glyphicon-user"></span> | ||
48 | <span class="video-small-block-text">{{ video.by }}</span> | ||
49 | </a> | ||
50 | </div> | ||
51 | |||
52 | <div class="col-xs-2 col-md-3 video-small-block video-small-block-share"> | ||
53 | <a class="option" (click)="showShareModal()" title="Share the video"> | ||
54 | <span class="glyphicon glyphicon-share"></span> | ||
55 | <span class="video-small-block-text">Share</span> | ||
56 | </a> | ||
57 | </div> | ||
58 | |||
59 | <div class="col-xs-2 col-md-3 video-small-block video-small-block-more"> | ||
60 | <div class="video-small-block-dropdown" dropdown dropup="true" placement="right"> | ||
61 | <a class="option" title="Access to more options" dropdownToggle> | ||
62 | <span class="glyphicon glyphicon-option-horizontal"></span> | ||
63 | <span class="video-small-block-text">More</span> | ||
64 | </a> | ||
65 | |||
66 | <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> | ||
67 | <li *ngIf="canUserUpdateVideo()" role="menuitem"> | ||
68 | <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]"> | ||
69 | <span class="glyphicon glyphicon-pencil"></span> Update | ||
70 | </a> | ||
71 | </li> | ||
72 | |||
73 | <li role="menuitem"> | ||
74 | <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)"> | ||
75 | <span class="glyphicon glyphicon-magnet"></span> Magnet | ||
76 | </a> | ||
77 | </li> | ||
78 | |||
79 | <li *ngIf="isUserLoggedIn()" role="menuitem"> | ||
80 | <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> | ||
81 | <span class="glyphicon glyphicon-alert"></span> Report | ||
82 | </a> | ||
83 | </li> | ||
84 | |||
85 | <li *ngIf="isVideoRemovable()" role="menuitem"> | ||
86 | <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> | ||
87 | <span class="glyphicon glyphicon-remove"></span> Delete | ||
88 | </a> | ||
89 | </li> | ||
90 | |||
91 | <li *ngIf="isVideoBlacklistable()" role="menuitem"> | ||
92 | <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> | ||
93 | <span class="glyphicon glyphicon-eye-close"></span> Blacklist | ||
94 | </a> | ||
95 | </li> | ||
96 | </ul> | ||
97 | </div> | ||
98 | </div> | ||
99 | |||
100 | <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating"> | ||
101 | <div class="video-small-block-like"> | ||
102 | <span | ||
103 | class="glyphicon glyphicon-thumbs-up" title="Like this video" | ||
104 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()" | ||
105 | ></span> | ||
106 | |||
107 | <span class="video-small-block-text"> | ||
108 | {{ video.likes }} | ||
109 | </span> | ||
110 | </div> | ||
111 | |||
112 | <div class="video-small-block-dislike"> | ||
113 | <span | ||
114 | class="glyphicon glyphicon-thumbs-down" title="Dislike this video" | ||
115 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()" | ||
116 | ></span> | ||
117 | |||
118 | <span class="video-small-block-text"> | ||
119 | {{ video.dislikes }} | ||
120 | </span> | ||
121 | </div> | ||
122 | </div> | ||
123 | </div> | ||
124 | |||
125 | <div class="row video-details"> | ||
126 | <div class="video-details-date-description col-xs-8 col-md-9"> | ||
127 | <div class="video-details-date"> | ||
128 | Published on {{ video.createdAt | date:'short' }} | ||
129 | </div> | ||
130 | |||
131 | <div class="video-details-description"> | ||
132 | {{ video.description }} | ||
133 | </div> | ||
134 | </div> | ||
135 | |||
136 | <div class="video-details-attributes col-xs-4 col-md-3"> | ||
137 | <div class="video-details-attribute"> | ||
138 | <span class="video-details-attribute-label"> | ||
139 | Category: | ||
140 | </span> | ||
141 | <span class="video-details-attribute-value"> | ||
142 | {{ video.categoryLabel }} | ||
143 | </span> | ||
144 | </div> | ||
145 | |||
146 | <div class="video-details-attribute"> | ||
147 | <span class="video-details-attribute-label"> | ||
148 | Licence: | ||
149 | </span> | ||
150 | <span class="video-details-attribute-value"> | ||
151 | {{ video.licenceLabel }} | ||
152 | </span> | ||
153 | </div> | ||
154 | |||
155 | <div class="video-details-attribute"> | ||
156 | <span class="video-details-attribute-label"> | ||
157 | Language: | ||
158 | </span> | ||
159 | <span class="video-details-attribute-value"> | ||
160 | {{ video.languageLabel }} | ||
161 | </span> | ||
162 | </div> | ||
163 | |||
164 | <div class="video-details-attribute"> | ||
165 | <span class="video-details-attribute-label"> | ||
166 | Tags: | ||
167 | </span> | ||
168 | |||
169 | <div class="video-details-tags"> | ||
170 | <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary"> | ||
171 | {{ tag }} | ||
172 | </a> | ||
173 | </div> | ||
174 | </div> | ||
175 | |||
176 | </div> | ||
177 | </div> | ||
178 | </div> | ||
179 | |||
180 | <ng-template [ngIf]="video !== null"> | ||
181 | <my-video-share #videoShareModal [video]="video"></my-video-share> | ||
182 | <my-video-magnet #videoMagnetModal [video]="video"></my-video-magnet> | ||
183 | <my-video-report #videoReportModal [video]="video"></my-video-report> | ||
184 | </ng-template> | ||
diff --git a/client/src/app/videos/video-watch/video-watch.component.scss b/client/src/app/videos/video-watch/video-watch.component.scss deleted file mode 100644 index 69661747c..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.scss +++ /dev/null | |||
@@ -1,245 +0,0 @@ | |||
1 | #video-container { | ||
2 | width: 100%; | ||
3 | height: 100%; | ||
4 | } | ||
5 | |||
6 | #video-not-found { | ||
7 | height: 300px; | ||
8 | line-height: 300px; | ||
9 | margin-top: 50px; | ||
10 | text-align: center; | ||
11 | font-weight: bold; | ||
12 | } | ||
13 | |||
14 | .embed-responsive { | ||
15 | height: 500px; | ||
16 | |||
17 | @media screen and (max-width: 600px) { | ||
18 | height: 300px; | ||
19 | } | ||
20 | } | ||
21 | |||
22 | #torrent-info { | ||
23 | font-size: 10px; | ||
24 | margin-top: 10px; | ||
25 | text-align: center; | ||
26 | |||
27 | div { | ||
28 | min-width: 60px; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | #video-info { | ||
33 | .video-name-views { | ||
34 | font-weight: bold; | ||
35 | font-size: 18px; | ||
36 | height: $video-watch-title-height; | ||
37 | line-height: $video-watch-title-height; | ||
38 | |||
39 | .video-name { | ||
40 | padding-left: $video-watch-info-padding-left; | ||
41 | } | ||
42 | |||
43 | .video-views { | ||
44 | text-align: right; | ||
45 | // Keep a symmetry with the video name | ||
46 | padding-right: $video-watch-info-padding-left | ||
47 | } | ||
48 | |||
49 | } | ||
50 | |||
51 | .video-small-blocks { | ||
52 | height: $video-watch-info-height; | ||
53 | color: $video-watch-info-color; | ||
54 | border-color: $video-watch-border-color; | ||
55 | border-width: 1px 0px; | ||
56 | border-style: solid; | ||
57 | |||
58 | .video-small-block { | ||
59 | height: $video-watch-info-height; | ||
60 | display: flex; | ||
61 | flex-direction: column; | ||
62 | justify-content: center; | ||
63 | text-align: center; | ||
64 | |||
65 | a { | ||
66 | cursor: pointer; | ||
67 | transition: color 0.3s; | ||
68 | white-space: nowrap; | ||
69 | overflow: hidden; | ||
70 | text-overflow: ellipsis; | ||
71 | |||
72 | &, &:hover { | ||
73 | color: inherit; | ||
74 | text-decoration:none; | ||
75 | } | ||
76 | |||
77 | &:hover { | ||
78 | color: #000 !important; | ||
79 | } | ||
80 | |||
81 | &:hover > .glyphicon { | ||
82 | opacity: 1 !important; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | .option .glyphicon { | ||
87 | font-size: 22px; | ||
88 | color: inherit; | ||
89 | opacity: 0.15; | ||
90 | margin-bottom: 10px; | ||
91 | transition: opacity 0.3s; | ||
92 | } | ||
93 | |||
94 | .video-small-block-text { | ||
95 | font-size: 15px; | ||
96 | font-weight: bold; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | .video-small-block:not(:last-child) { | ||
101 | border-width: 0 1px 0 0; | ||
102 | border-color: $video-watch-border-color; | ||
103 | border-style: solid; | ||
104 | } | ||
105 | |||
106 | .video-small-block-author, .video-small-block-more { | ||
107 | a.option { | ||
108 | display: block; | ||
109 | |||
110 | .glyphicon { | ||
111 | display: block; | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | |||
116 | .video-small-block-share, .video-small-block-more { | ||
117 | a.option { | ||
118 | display: block; | ||
119 | |||
120 | .glyphicon { | ||
121 | display: block; | ||
122 | } | ||
123 | } | ||
124 | } | ||
125 | |||
126 | .video-small-block-more .video-small-block-dropdown { | ||
127 | position: relative; | ||
128 | |||
129 | .dropdown-item .glyphicon { | ||
130 | margin-right: 5px; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | .video-small-block-rating { | ||
135 | |||
136 | .video-small-block-like { | ||
137 | margin-bottom: 10px; | ||
138 | } | ||
139 | |||
140 | .video-small-block-text { | ||
141 | vertical-align: top; | ||
142 | } | ||
143 | |||
144 | .glyphicon { | ||
145 | font-size: 18px; | ||
146 | margin: 0 10px 0 0; | ||
147 | opacity: 0.3; | ||
148 | } | ||
149 | |||
150 | .interactive { | ||
151 | cursor: pointer; | ||
152 | transition: opacity, color 0.3s; | ||
153 | |||
154 | &.activated, &:hover { | ||
155 | opacity: 1; | ||
156 | color: #000; | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | |||
162 | .video-details { | ||
163 | margin-top: 30px; | ||
164 | |||
165 | .video-details-date-description { | ||
166 | padding-left: $video-watch-info-padding-left; | ||
167 | |||
168 | .video-details-date { | ||
169 | font-weight: bold; | ||
170 | margin-bottom: 30px; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | .video-details-attributes { | ||
175 | font-weight: bold; | ||
176 | font-size: 12px; | ||
177 | |||
178 | .video-details-attribute-label { | ||
179 | color: $video-watch-info-color; | ||
180 | display: inline-block; | ||
181 | width: 60px; | ||
182 | margin-right: 5px; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | .video-details-tags { | ||
187 | display: inline-block; | ||
188 | |||
189 | a { | ||
190 | display: inline-block; | ||
191 | margin-right: 3px; | ||
192 | font-size: 11px; | ||
193 | } | ||
194 | } | ||
195 | } | ||
196 | |||
197 | @media screen and (max-width: 400px) { | ||
198 | .video-name-views { | ||
199 | font-size: 16px !important; | ||
200 | } | ||
201 | } | ||
202 | |||
203 | @media screen and (max-width: 800px) { | ||
204 | .video-name-views { | ||
205 | .video-name { | ||
206 | padding-left: 5px; | ||
207 | padding-right: 0px; | ||
208 | } | ||
209 | |||
210 | .video-views { | ||
211 | padding-left: 0px; | ||
212 | padding-right: 5px; | ||
213 | } | ||
214 | } | ||
215 | |||
216 | .video-small-blocks { | ||
217 | a, .video-small-block-text { | ||
218 | font-size: 13px !important; | ||
219 | } | ||
220 | |||
221 | .glyphicon { | ||
222 | font-size: 18px !important; | ||
223 | } | ||
224 | |||
225 | .video-small-block-author { | ||
226 | padding-left: 10px; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | .video-details { | ||
231 | .video-details-date-description { | ||
232 | padding-left: 10px; | ||
233 | font-size: 13px !important; | ||
234 | } | ||
235 | |||
236 | .video-details-attributes { | ||
237 | font-size: 11px !important; | ||
238 | |||
239 | .video-details-attribute-label { | ||
240 | width: 50px; | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | } | ||
245 | } | ||
diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts deleted file mode 100644 index db3e1cdd6..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ /dev/null | |||
@@ -1,299 +0,0 @@ | |||
1 | import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Observable } from 'rxjs/Observable' | ||
4 | import { Subscription } from 'rxjs/Subscription' | ||
5 | |||
6 | import videojs from 'video.js' | ||
7 | import '../../../assets/player/peertube-videojs-plugin' | ||
8 | |||
9 | import { MetaService } from '@ngx-meta/core' | ||
10 | import { NotificationsService } from 'angular2-notifications' | ||
11 | |||
12 | import { AuthService, ConfirmService } from '../../core' | ||
13 | import { VideoMagnetComponent } from './video-magnet.component' | ||
14 | import { VideoShareComponent } from './video-share.component' | ||
15 | import { VideoReportComponent } from './video-report.component' | ||
16 | import { Video, VideoService } from '../shared' | ||
17 | import { WebTorrentService } from './webtorrent.service' | ||
18 | import { UserVideoRateType, VideoRateType } from '../../../../../shared' | ||
19 | |||
20 | @Component({ | ||
21 | selector: 'my-video-watch', | ||
22 | templateUrl: './video-watch.component.html', | ||
23 | styleUrls: [ './video-watch.component.scss' ] | ||
24 | }) | ||
25 | export class VideoWatchComponent implements OnInit, OnDestroy { | ||
26 | @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent | ||
27 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent | ||
28 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent | ||
29 | |||
30 | downloadSpeed: number | ||
31 | error = false | ||
32 | loading = false | ||
33 | numPeers: number | ||
34 | player: videojs.Player | ||
35 | playerElement: HTMLMediaElement | ||
36 | uploadSpeed: number | ||
37 | userRating: UserVideoRateType = null | ||
38 | video: Video = null | ||
39 | videoNotFound = false | ||
40 | |||
41 | private paramsSub: Subscription | ||
42 | |||
43 | constructor ( | ||
44 | private elementRef: ElementRef, | ||
45 | private route: ActivatedRoute, | ||
46 | private router: Router, | ||
47 | private videoService: VideoService, | ||
48 | private confirmService: ConfirmService, | ||
49 | private metaService: MetaService, | ||
50 | private authService: AuthService, | ||
51 | private notificationsService: NotificationsService | ||
52 | ) {} | ||
53 | |||
54 | ngOnInit () { | ||
55 | this.paramsSub = this.route.params.subscribe(routeParams => { | ||
56 | let uuid = routeParams['uuid'] | ||
57 | this.videoService.getVideo(uuid).subscribe( | ||
58 | video => this.onVideoFetched(video), | ||
59 | |||
60 | error => { | ||
61 | console.error(error) | ||
62 | this.videoNotFound = true | ||
63 | } | ||
64 | ) | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | ngOnDestroy () { | ||
69 | // Remove player if it exists | ||
70 | if (this.videoNotFound === false) { | ||
71 | videojs(this.playerElement).dispose() | ||
72 | } | ||
73 | |||
74 | // Unsubscribe subscriptions | ||
75 | this.paramsSub.unsubscribe() | ||
76 | } | ||
77 | |||
78 | setLike () { | ||
79 | if (this.isUserLoggedIn() === false) return | ||
80 | // Already liked this video | ||
81 | if (this.userRating === 'like') return | ||
82 | |||
83 | this.videoService.setVideoLike(this.video.id) | ||
84 | .subscribe( | ||
85 | () => { | ||
86 | // Update the video like attribute | ||
87 | this.updateVideoRating(this.userRating, 'like') | ||
88 | this.userRating = 'like' | ||
89 | }, | ||
90 | |||
91 | err => this.notificationsService.error('Error', err.message) | ||
92 | ) | ||
93 | } | ||
94 | |||
95 | setDislike () { | ||
96 | if (this.isUserLoggedIn() === false) return | ||
97 | // Already disliked this video | ||
98 | if (this.userRating === 'dislike') return | ||
99 | |||
100 | this.videoService.setVideoDislike(this.video.id) | ||
101 | .subscribe( | ||
102 | () => { | ||
103 | // Update the video dislike attribute | ||
104 | this.updateVideoRating(this.userRating, 'dislike') | ||
105 | this.userRating = 'dislike' | ||
106 | }, | ||
107 | |||
108 | err => this.notificationsService.error('Error', err.message) | ||
109 | ) | ||
110 | } | ||
111 | |||
112 | removeVideo (event: Event) { | ||
113 | event.preventDefault() | ||
114 | |||
115 | this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( | ||
116 | res => { | ||
117 | if (res === false) return | ||
118 | |||
119 | this.videoService.removeVideo(this.video.id) | ||
120 | .subscribe( | ||
121 | status => { | ||
122 | this.notificationsService.success('Success', `Video ${this.video.name} deleted.`) | ||
123 | // Go back to the video-list. | ||
124 | this.router.navigate(['/videos/list']) | ||
125 | }, | ||
126 | |||
127 | error => this.notificationsService.error('Error', error.text) | ||
128 | ) | ||
129 | } | ||
130 | ) | ||
131 | } | ||
132 | |||
133 | blacklistVideo (event: Event) { | ||
134 | event.preventDefault() | ||
135 | |||
136 | this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( | ||
137 | res => { | ||
138 | if (res === false) return | ||
139 | |||
140 | this.videoService.blacklistVideo(this.video.id) | ||
141 | .subscribe( | ||
142 | status => { | ||
143 | this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`) | ||
144 | this.router.navigate(['/videos/list']) | ||
145 | }, | ||
146 | |||
147 | error => this.notificationsService.error('Error', error.text) | ||
148 | ) | ||
149 | } | ||
150 | ) | ||
151 | } | ||
152 | |||
153 | showReportModal (event: Event) { | ||
154 | event.preventDefault() | ||
155 | this.videoReportModal.show() | ||
156 | } | ||
157 | |||
158 | showShareModal () { | ||
159 | this.videoShareModal.show() | ||
160 | } | ||
161 | |||
162 | showMagnetUriModal (event: Event) { | ||
163 | event.preventDefault() | ||
164 | this.videoMagnetModal.show() | ||
165 | } | ||
166 | |||
167 | isUserLoggedIn () { | ||
168 | return this.authService.isLoggedIn() | ||
169 | } | ||
170 | |||
171 | canUserUpdateVideo () { | ||
172 | return this.video.isUpdatableBy(this.authService.getUser()) | ||
173 | } | ||
174 | |||
175 | isVideoRemovable () { | ||
176 | return this.video.isRemovableBy(this.authService.getUser()) | ||
177 | } | ||
178 | |||
179 | isVideoBlacklistable () { | ||
180 | return this.video.isBlackistableBy(this.authService.getUser()) | ||
181 | } | ||
182 | |||
183 | private handleError (err: any) { | ||
184 | const errorMessage: string = typeof err === 'string' ? err : err.message | ||
185 | let message = '' | ||
186 | |||
187 | if (errorMessage.indexOf('http error') !== -1) { | ||
188 | message = 'Cannot fetch video from server, maybe down.' | ||
189 | } else { | ||
190 | message = errorMessage | ||
191 | } | ||
192 | |||
193 | this.notificationsService.error('Error', message) | ||
194 | } | ||
195 | |||
196 | private checkUserRating () { | ||
197 | // Unlogged users do not have ratings | ||
198 | if (this.isUserLoggedIn() === false) return | ||
199 | |||
200 | this.videoService.getUserVideoRating(this.video.id) | ||
201 | .subscribe( | ||
202 | ratingObject => { | ||
203 | if (ratingObject) { | ||
204 | this.userRating = ratingObject.rating | ||
205 | } | ||
206 | }, | ||
207 | |||
208 | err => this.notificationsService.error('Error', err.message) | ||
209 | ) | ||
210 | } | ||
211 | |||
212 | private onVideoFetched (video: Video) { | ||
213 | this.video = video | ||
214 | |||
215 | let observable | ||
216 | if (this.video.isVideoNSFWForUser(this.authService.getUser())) { | ||
217 | observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW') | ||
218 | } else { | ||
219 | observable = Observable.of(true) | ||
220 | } | ||
221 | |||
222 | observable.subscribe( | ||
223 | res => { | ||
224 | if (res === false) { | ||
225 | return this.router.navigate([ '/videos/list' ]) | ||
226 | } | ||
227 | |||
228 | this.playerElement = this.elementRef.nativeElement.querySelector('#video-container') | ||
229 | |||
230 | const videojsOptions = { | ||
231 | controls: true, | ||
232 | autoplay: true, | ||
233 | plugins: { | ||
234 | peertube: { | ||
235 | videoFiles: this.video.files, | ||
236 | playerElement: this.playerElement, | ||
237 | autoplay: true, | ||
238 | peerTubeLink: false | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | const self = this | ||
244 | videojs(this.playerElement, videojsOptions, function () { | ||
245 | self.player = this | ||
246 | this.on('customError', (event, data) => { | ||
247 | self.handleError(data.err) | ||
248 | }) | ||
249 | |||
250 | this.on('torrentInfo', (event, data) => { | ||
251 | self.downloadSpeed = data.downloadSpeed | ||
252 | self.numPeers = data.numPeers | ||
253 | self.uploadSpeed = data.uploadSpeed | ||
254 | }) | ||
255 | }) | ||
256 | |||
257 | this.setOpenGraphTags() | ||
258 | this.checkUserRating() | ||
259 | } | ||
260 | ) | ||
261 | } | ||
262 | |||
263 | private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { | ||
264 | let likesToIncrement = 0 | ||
265 | let dislikesToIncrement = 0 | ||
266 | |||
267 | if (oldRating) { | ||
268 | if (oldRating === 'like') likesToIncrement-- | ||
269 | if (oldRating === 'dislike') dislikesToIncrement-- | ||
270 | } | ||
271 | |||
272 | if (newRating === 'like') likesToIncrement++ | ||
273 | if (newRating === 'dislike') dislikesToIncrement++ | ||
274 | |||
275 | this.video.likes += likesToIncrement | ||
276 | this.video.dislikes += dislikesToIncrement | ||
277 | } | ||
278 | |||
279 | private setOpenGraphTags () { | ||
280 | this.metaService.setTitle(this.video.name) | ||
281 | |||
282 | this.metaService.setTag('og:type', 'video') | ||
283 | |||
284 | this.metaService.setTag('og:title', this.video.name) | ||
285 | this.metaService.setTag('name', this.video.name) | ||
286 | |||
287 | this.metaService.setTag('og:description', this.video.description) | ||
288 | this.metaService.setTag('description', this.video.description) | ||
289 | |||
290 | this.metaService.setTag('og:image', this.video.previewPath) | ||
291 | |||
292 | this.metaService.setTag('og:duration', this.video.duration.toString()) | ||
293 | |||
294 | this.metaService.setTag('og:site_name', 'PeerTube') | ||
295 | |||
296 | this.metaService.setTag('og:url', window.location.href) | ||
297 | this.metaService.setTag('url', window.location.href) | ||
298 | } | ||
299 | } | ||