diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-12-06 17:15:59 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-12-06 17:15:59 +0100 |
commit | b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2 (patch) | |
tree | fffcdf00d7b475c5c2de7456a7ef3c4d47c71865 /client/src/app | |
parent | ce0e281d46a7b574dcccb47958743656532bd312 (diff) | |
download | PeerTube-b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2.tar.gz PeerTube-b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2.tar.zst PeerTube-b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2.zip |
Begin video watch design
Diffstat (limited to 'client/src/app')
16 files changed, 244 insertions, 401 deletions
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts new file mode 100644 index 000000000..0b008188a --- /dev/null +++ b/client/src/app/shared/account/account.model.ts | |||
@@ -0,0 +1,20 @@ | |||
1 | import { Account as ServerAccount } from '../../../../../shared/models/accounts/account.model' | ||
2 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | ||
3 | |||
4 | export class Account implements ServerAccount { | ||
5 | id: number | ||
6 | uuid: string | ||
7 | name: string | ||
8 | host: string | ||
9 | followingCount: number | ||
10 | followersCount: number | ||
11 | createdAt: Date | ||
12 | updatedAt: Date | ||
13 | avatar: Avatar | ||
14 | |||
15 | static GET_ACCOUNT_AVATAR_PATH (account: Account) { | ||
16 | if (account && account.avatar) return account.avatar.path | ||
17 | |||
18 | return API_URL + '/client/assets/images/default-avatar.png' | ||
19 | } | ||
20 | } | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 86e1a380e..bd9aee345 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -20,6 +20,7 @@ import { RestExtractor, RestService } from './rest' | |||
20 | import { UserService } from './users' | 20 | import { UserService } from './users' |
21 | import { VideoAbuseService } from './video-abuse' | 21 | import { VideoAbuseService } from './video-abuse' |
22 | import { VideoBlacklistService } from './video-blacklist' | 22 | import { VideoBlacklistService } from './video-blacklist' |
23 | import { VideoMiniatureComponent } from './video/video-miniature.component' | ||
23 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' | 24 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' |
24 | import { VideoService } from './video/video.service' | 25 | import { VideoService } from './video/video.service' |
25 | 26 | ||
@@ -44,6 +45,7 @@ import { VideoService } from './video/video.service' | |||
44 | declarations: [ | 45 | declarations: [ |
45 | LoaderComponent, | 46 | LoaderComponent, |
46 | VideoThumbnailComponent, | 47 | VideoThumbnailComponent, |
48 | VideoMiniatureComponent, | ||
47 | NumberFormatterPipe, | 49 | NumberFormatterPipe, |
48 | FromNowPipe | 50 | FromNowPipe |
49 | ], | 51 | ], |
@@ -66,6 +68,7 @@ import { VideoService } from './video/video.service' | |||
66 | 68 | ||
67 | LoaderComponent, | 69 | LoaderComponent, |
68 | VideoThumbnailComponent, | 70 | VideoThumbnailComponent, |
71 | VideoMiniatureComponent, | ||
69 | 72 | ||
70 | NumberFormatterPipe, | 73 | NumberFormatterPipe, |
71 | FromNowPipe | 74 | FromNowPipe |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index b1c323114..b4d13f37c 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' | 1 | import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' |
2 | import { Account } from '../../../../../shared/models/accounts' | 2 | import { Account } from '../account/account.model' |
3 | 3 | ||
4 | export type UserConstructorHash = { | 4 | export type UserConstructorHash = { |
5 | id: number, | 5 | id: number, |
@@ -52,8 +52,6 @@ export class User implements UserServerModel { | |||
52 | } | 52 | } |
53 | 53 | ||
54 | getAvatarPath () { | 54 | getAvatarPath () { |
55 | if (this.account && this.account.avatar) return this.account.avatar.path | 55 | return Account.GET_ACCOUNT_AVATAR_PATH(this.account) |
56 | |||
57 | return API_URL + '/client/assets/images/default-avatar.png' | ||
58 | } | 56 | } |
59 | } | 57 | } |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index bd4f6b1f8..5d07a276b 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -12,7 +12,7 @@ | |||
12 | > | 12 | > |
13 | <my-video-miniature | 13 | <my-video-miniature |
14 | class="ng-animate" | 14 | class="ng-animate" |
15 | *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" | 15 | *ngFor="let video of videos" [video]="video" [user]="user" |
16 | > | 16 | > |
17 | </my-video-miniature> | 17 | </my-video-miniature> |
18 | </div> | 18 | </div> |
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index 93c380b73..1a956da7c 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { Account } from '../../../../../shared/models/accounts' | ||
1 | import { Video } from '../../shared/video/video.model' | 2 | import { Video } from '../../shared/video/video.model' |
2 | import { AuthUser } from '../../core' | 3 | import { AuthUser } from '../../core' |
3 | import { | 4 | import { |
@@ -10,7 +11,7 @@ import { | |||
10 | } from '../../../../../shared' | 11 | } from '../../../../../shared' |
11 | 12 | ||
12 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 13 | export class VideoDetails extends Video implements VideoDetailsServerModel { |
13 | account: string | 14 | accountName: string |
14 | by: string | 15 | by: string |
15 | createdAt: Date | 16 | createdAt: Date |
16 | updatedAt: Date | 17 | updatedAt: Date |
@@ -44,6 +45,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
44 | channel: VideoChannel | 45 | channel: VideoChannel |
45 | privacy: VideoPrivacy | 46 | privacy: VideoPrivacy |
46 | privacyLabel: string | 47 | privacyLabel: string |
48 | account: Account | ||
47 | 49 | ||
48 | constructor (hash: VideoDetailsServerModel) { | 50 | constructor (hash: VideoDetailsServerModel) { |
49 | super(hash) | 51 | super(hash) |
@@ -53,6 +55,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
53 | this.descriptionPath = hash.descriptionPath | 55 | this.descriptionPath = hash.descriptionPath |
54 | this.files = hash.files | 56 | this.files = hash.files |
55 | this.channel = hash.channel | 57 | this.channel = hash.channel |
58 | this.account = hash.account | ||
56 | } | 59 | } |
57 | 60 | ||
58 | getAppropriateMagnetUri (actualDownloadSpeed = 0) { | 61 | getAppropriateMagnetUri (actualDownloadSpeed = 0) { |
@@ -71,7 +74,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
71 | } | 74 | } |
72 | 75 | ||
73 | isRemovableBy (user: AuthUser) { | 76 | isRemovableBy (user: AuthUser) { |
74 | return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) | 77 | return user && this.isLocal === true && (this.accountName === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) |
75 | } | 78 | } |
76 | 79 | ||
77 | isBlackistableBy (user: AuthUser) { | 80 | isBlackistableBy (user: AuthUser) { |
@@ -79,6 +82,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
79 | } | 82 | } |
80 | 83 | ||
81 | isUpdatableBy (user: AuthUser) { | 84 | isUpdatableBy (user: AuthUser) { |
82 | return user && this.isLocal === true && user.username === this.account | 85 | return user && this.isLocal === true && user.username === this.accountName |
83 | } | 86 | } |
84 | } | 87 | } |
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index 7ac017235..7ac017235 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html | |||
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss index 37e84897b..37e84897b 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.scss +++ b/client/src/app/shared/video/video-miniature.component.scss | |||
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index e8fc8e911..4d79a74bb 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { User } from '../../../shared' | 2 | import { User } from '../users' |
3 | import { SortField } from '../../../shared/video/sort-field.type' | 3 | import { Video } from './video.model' |
4 | import { Video } from '../../../shared/video/video.model' | ||
5 | 4 | ||
6 | @Component({ | 5 | @Component({ |
7 | selector: 'my-video-miniature', | 6 | selector: 'my-video-miniature', |
@@ -9,7 +8,6 @@ import { Video } from '../../../shared/video/video.model' | |||
9 | templateUrl: './video-miniature.component.html' | 8 | templateUrl: './video-miniature.component.html' |
10 | }) | 9 | }) |
11 | export class VideoMiniatureComponent { | 10 | export class VideoMiniatureComponent { |
12 | @Input() currentSort: SortField | ||
13 | @Input() user: User | 11 | @Input() user: User |
14 | @Input() video: Video | 12 | @Input() video: Video |
15 | 13 | ||
diff --git a/client/src/app/shared/video/video-pagination.model.ts b/client/src/app/shared/video/video-pagination.model.ts index 9e71769cb..e9db61596 100644 --- a/client/src/app/shared/video/video-pagination.model.ts +++ b/client/src/app/shared/video/video-pagination.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export interface VideoPagination { | 1 | export interface VideoPagination { |
2 | currentPage: number | 2 | currentPage: number |
3 | itemsPerPage: number | 3 | itemsPerPage: number |
4 | totalItems: number | 4 | totalItems?: number |
5 | } | 5 | } |
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index 6929c8755..d86ef8f92 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import { Video as VideoServerModel } from '../../../../../shared' | 1 | import { Video as VideoServerModel } from '../../../../../shared' |
2 | import { User } from '../' | 2 | import { User } from '../' |
3 | import { Account } from '../../../../../shared/models/accounts' | ||
3 | 4 | ||
4 | export class Video implements VideoServerModel { | 5 | export class Video implements VideoServerModel { |
5 | account: string | 6 | accountName: string |
6 | by: string | 7 | by: string |
7 | createdAt: Date | 8 | createdAt: Date |
8 | updatedAt: Date | 9 | updatedAt: Date |
@@ -31,6 +32,7 @@ export class Video implements VideoServerModel { | |||
31 | likes: number | 32 | likes: number |
32 | dislikes: number | 33 | dislikes: number |
33 | nsfw: boolean | 34 | nsfw: boolean |
35 | account: Account | ||
34 | 36 | ||
35 | private static createByString (account: string, serverHost: string) { | 37 | private static createByString (account: string, serverHost: string) { |
36 | return account + '@' + serverHost | 38 | return account + '@' + serverHost |
@@ -52,7 +54,7 @@ export class Video implements VideoServerModel { | |||
52 | absoluteAPIUrl = window.location.origin | 54 | absoluteAPIUrl = window.location.origin |
53 | } | 55 | } |
54 | 56 | ||
55 | this.account = hash.account | 57 | this.accountName = hash.accountName |
56 | this.createdAt = new Date(hash.createdAt.toString()) | 58 | this.createdAt = new Date(hash.createdAt.toString()) |
57 | this.categoryLabel = hash.categoryLabel | 59 | this.categoryLabel = hash.categoryLabel |
58 | this.category = hash.category | 60 | this.category = hash.category |
@@ -80,7 +82,7 @@ export class Video implements VideoServerModel { | |||
80 | this.dislikes = hash.dislikes | 82 | this.dislikes = hash.dislikes |
81 | this.nsfw = hash.nsfw | 83 | this.nsfw = hash.nsfw |
82 | 84 | ||
83 | this.by = Video.createByString(hash.account, hash.serverHost) | 85 | this.by = Video.createByString(hash.accountName, hash.serverHost) |
84 | } | 86 | } |
85 | 87 | ||
86 | isVideoNSFWForUser (user: User) { | 88 | isVideoNSFWForUser (user: User) { |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index aa1f2f77e..f31e82bff 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -1,18 +1,3 @@ | |||
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.serverHost }} 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"> | 1 | <div class="row"> |
17 | <!-- We need the video container for videojs so we just hide it --> | 2 | <!-- We need the video container for videojs so we just hide it --> |
18 | <div [hidden]="videoNotFound" id="video-container"> | 3 | <div [hidden]="videoNotFound" id="video-container"> |
@@ -23,167 +8,153 @@ | |||
23 | </div> | 8 | </div> |
24 | 9 | ||
25 | <!-- Video information --> | 10 | <!-- Video information --> |
26 | <div *ngIf="video !== null" id="video-info"> | 11 | <div *ngIf="video" class="margin-content video-bottom"> |
27 | <div class="row video-name-views"> | 12 | <div class="video-info"> |
28 | <div class="col-xs-8 col-md-8 video-name"> | 13 | <div class="video-info-name-actions"> |
29 | {{ video.name }} | 14 | <div class="video-info-name">{{ video.name }}</div> |
30 | </div> | 15 | |
31 | 16 | <div class="video-info-actions"> | |
32 | <div class="col-xs-4 col-md-4 pull-right video-views"> | 17 | <div class="action-button"> |
33 | {{ video.views}} views | 18 | <span |
34 | </div> | 19 | class="icon icon-like" title="Like this video" |
35 | </div> | 20 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()" |
21 | ></span> | ||
22 | </div> | ||
36 | 23 | ||
37 | <div class="row video-small-blocks"> | 24 | <div class="action-button"> |
38 | <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-account"> | 25 | <span |
39 | <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'account', search: video.account }]"> | 26 | class="icon icon-dislike" title="Dislike this video" |
40 | <span class="glyphicon glyphicon-user"></span> | 27 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()" |
41 | <span class="video-small-block-text">{{ video.by }}</span> | 28 | ></span> |
42 | </a> | 29 | </div> |
43 | </div> | ||
44 | 30 | ||
45 | <div class="col-xs-2 col-md-3 video-small-block video-small-block-share"> | 31 | <div (click)="showShareModal()" class="action-button"> |
46 | <a class="option" (click)="showShareModal()" title="Share the video"> | 32 | <span class="icon icon-share"></span> |
47 | <span class="glyphicon glyphicon-share"></span> | 33 | Share |
48 | <span class="hidden-xs video-small-block-text">Share</span> | 34 | </div> |
49 | </a> | ||
50 | </div> | ||
51 | 35 | ||
52 | <div class="col-xs-2 col-md-3 video-small-block video-small-block-more"> | 36 | <div class="action-more" dropdown dropup="true" placement="right"> |
53 | <div class="video-small-block-dropdown" dropdown dropup="true" placement="right"> | 37 | <div class="action-button" dropdownToggle> |
54 | <a class="option" title="Access to more options" dropdownToggle> | 38 | <span class="icon icon-more"></span> |
55 | <span class="glyphicon glyphicon-option-horizontal"></span> | 39 | </div> |
56 | <span class="hidden-xs video-small-block-text">More</span> | 40 | |
57 | </a> | 41 | <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> |
58 | 42 | <li *ngIf="canUserUpdateVideo()" role="menuitem"> | |
59 | <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> | 43 | <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]"> |
60 | <li *ngIf="canUserUpdateVideo()" role="menuitem"> | 44 | <span class="glyphicon glyphicon-pencil"></span> Update |
61 | <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]"> | 45 | </a> |
62 | <span class="glyphicon glyphicon-pencil"></span> Update | 46 | </li> |
63 | </a> | 47 | |
64 | </li> | 48 | <li role="menuitem"> |
65 | 49 | <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)"> | |
66 | <li role="menuitem"> | 50 | <span class="glyphicon glyphicon-download-alt"></span> Download |
67 | <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)"> | 51 | </a> |
68 | <span class="glyphicon glyphicon-download-alt"></span> Download | 52 | </li> |
69 | </a> | 53 | |
70 | </li> | 54 | <li *ngIf="isUserLoggedIn()" role="menuitem"> |
71 | 55 | <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> | |
72 | <li *ngIf="isUserLoggedIn()" role="menuitem"> | 56 | <span class="glyphicon glyphicon-alert"></span> Report |
73 | <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> | 57 | </a> |
74 | <span class="glyphicon glyphicon-alert"></span> Report | 58 | </li> |
75 | </a> | 59 | |
76 | </li> | 60 | <li *ngIf="isVideoRemovable()" role="menuitem"> |
77 | 61 | <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> | |
78 | <li *ngIf="isVideoRemovable()" role="menuitem"> | 62 | <span class="glyphicon glyphicon-remove"></span> Delete |
79 | <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> | 63 | </a> |
80 | <span class="glyphicon glyphicon-remove"></span> Delete | 64 | </li> |
81 | </a> | 65 | |
82 | </li> | 66 | <li *ngIf="isVideoBlacklistable()" role="menuitem"> |
83 | 67 | <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> | |
84 | <li *ngIf="isVideoBlacklistable()" role="menuitem"> | 68 | <span class="glyphicon glyphicon-eye-close"></span> Blacklist |
85 | <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> | 69 | </a> |
86 | <span class="glyphicon glyphicon-eye-close"></span> Blacklist | 70 | </li> |
87 | </a> | 71 | </ul> |
88 | </li> | 72 | </div> |
89 | </ul> | ||
90 | </div> | 73 | </div> |
91 | </div> | 74 | </div> |
92 | 75 | ||
93 | <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating"> | 76 | <div class="video-info-date-views"> |
94 | <div class="video-small-block-like"> | 77 | {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views |
95 | <span | 78 | </div> |
96 | class="glyphicon glyphicon-thumbs-up" title="Like this video" | ||
97 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()" | ||
98 | ></span> | ||
99 | |||
100 | <span class="video-small-block-text"> | ||
101 | {{ video.likes }} | ||
102 | </span> | ||
103 | </div> | ||
104 | |||
105 | <div class="video-small-block-dislike"> | ||
106 | <span | ||
107 | class="glyphicon glyphicon-thumbs-down" title="Dislike this video" | ||
108 | [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()" | ||
109 | ></span> | ||
110 | 79 | ||
111 | <span class="video-small-block-text"> | 80 | <div class="video-info-channel"> |
112 | {{ video.dislikes }} | 81 | {{ video.channel.name }} |
113 | </span> | 82 | <!-- Here will be the subscribe button --> |
114 | </div> | ||
115 | </div> | 83 | </div> |
116 | </div> | ||
117 | 84 | ||
118 | <div class="row video-details"> | 85 | <div class="video-info-by"> |
119 | <div class="video-details-date-description col-xs-8 col-md-9"> | 86 | By {{ video.by }} |
120 | <div class="video-details-date"> | 87 | <img [src]="getAvatarPath()" alt="Account avatar" /> |
121 | Published on {{ video.createdAt | date:'short' }} | 88 | </div> |
122 | </div> | ||
123 | 89 | ||
124 | <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div> | 90 | <div class="video-info-description"> |
91 | <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div> | ||
125 | 92 | ||
126 | <div class="video-details-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()"> | 93 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()"> |
127 | Show more | 94 | Show more |
128 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> | 95 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> |
129 | <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader> | 96 | <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader> |
130 | </div> | 97 | </div> |
131 | 98 | ||
132 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-details-description-more"> | 99 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> |
133 | Show less | 100 | Show less |
134 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> | 101 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> |
135 | </div> | 102 | </div> |
136 | </div> | 103 | </div> |
137 | 104 | ||
138 | <div class="video-details-attributes col-xs-4 col-md-3"> | 105 | <div class="video-attributes"> |
139 | <div class="video-details-attribute"> | 106 | <div class="video-attribute"> |
140 | <span class="video-details-attribute-label"> | 107 | <span class="video-attribute-label"> |
141 | Privacy: | 108 | Privacy |
142 | </span> | 109 | </span> |
143 | <span class="video-details-attribute-value"> | 110 | <span class="video-attribute-value"> |
144 | {{ video.privacyLabel }} | 111 | {{ video.privacyLabel }} |
145 | </span> | 112 | </span> |
146 | </div> | 113 | </div> |
147 | 114 | ||
148 | <div class="video-details-attribute"> | 115 | <div class="video-attribute"> |
149 | <span class="video-details-attribute-label"> | 116 | <span class="video-attribute-label"> |
150 | Category: | 117 | Category |
151 | </span> | 118 | </span> |
152 | <span class="video-details-attribute-value"> | 119 | <span class="video-attribute-value"> |
153 | {{ video.categoryLabel }} | 120 | {{ video.categoryLabel }} |
154 | </span> | 121 | </span> |
155 | </div> | 122 | </div> |
156 | 123 | ||
157 | <div class="video-details-attribute"> | 124 | <div class="video-attribute"> |
158 | <span class="video-details-attribute-label"> | 125 | <span class="video-attribute-label"> |
159 | Licence: | 126 | Licence |
160 | </span> | 127 | </span> |
161 | <span class="video-details-attribute-value"> | 128 | <span class="video-attribute-value"> |
162 | {{ video.licenceLabel }} | 129 | {{ video.licenceLabel }} |
163 | </span> | 130 | </span> |
164 | </div> | 131 | </div> |
165 | 132 | ||
166 | <div class="video-details-attribute"> | 133 | <div class="video-attribute"> |
167 | <span class="video-details-attribute-label"> | 134 | <span class="video-attribute-label"> |
168 | Language: | 135 | Language |
169 | </span> | 136 | </span> |
170 | <span class="video-details-attribute-value"> | 137 | <span class="video-attribute-value"> |
171 | {{ video.languageLabel }} | 138 | {{ video.languageLabel }} |
172 | </span> | 139 | </span> |
173 | </div> | 140 | </div> |
174 | 141 | ||
175 | <div class="video-details-attribute"> | 142 | <div class="video-attribute"> |
176 | <span class="video-details-attribute-label"> | 143 | <span class="video-attribute-label"> |
177 | Tags: | 144 | Tags |
178 | </span> | 145 | </span> |
179 | 146 | ||
180 | <div class="video-details-tags"> | 147 | <span class="video-attribute-value"> |
181 | <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary"> | 148 | {{ getVideoTags() }} |
182 | {{ tag }} | 149 | </span> |
183 | </a> | ||
184 | </div> | ||
185 | </div> | 150 | </div> |
151 | </div> | ||
152 | |||
153 | </div> | ||
186 | 154 | ||
155 | <div class="other-videos"> | ||
156 | <div *ngFor="let video of otherVideos"> | ||
157 | <my-video-miniature [video]="video" [user]="user"></my-video-miniature> | ||
187 | </div> | 158 | </div> |
188 | </div> | 159 | </div> |
189 | </div> | 160 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index 06c2de7c6..7bcfeb7c3 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -17,167 +17,108 @@ | |||
17 | font-weight: bold; | 17 | font-weight: bold; |
18 | } | 18 | } |
19 | 19 | ||
20 | #torrent-info { | 20 | .video-bottom { |
21 | font-size: 10px; | 21 | margin-top: 40px; |
22 | margin-top: 10px; | 22 | display: flex; |
23 | text-align: center; | ||
24 | |||
25 | div { | ||
26 | min-width: 60px; | ||
27 | } | ||
28 | } | ||
29 | |||
30 | #video-info { | ||
31 | .video-name-views { | ||
32 | font-weight: bold; | ||
33 | font-size: 18px; | ||
34 | min-height: $video-watch-title-height; | ||
35 | display: flex; | ||
36 | align-items: center; | ||
37 | |||
38 | .video-name { | ||
39 | padding-left: $video-watch-info-padding-left; | ||
40 | } | ||
41 | 23 | ||
42 | .video-views { | 24 | .video-info { |
43 | text-align: right; | 25 | flex-grow: 1; |
44 | // Keep a symmetry with the video name | 26 | margin-right: 28px; |
45 | padding-right: $video-watch-info-padding-left | ||
46 | } | ||
47 | 27 | ||
48 | } | 28 | .video-info-name-actions { |
29 | display: flex; | ||
30 | align-items: center; | ||
49 | 31 | ||
50 | .video-small-blocks { | 32 | .video-info-name { |
51 | height: $video-watch-info-height; | 33 | font-size: 27px; |
52 | color: $video-watch-info-color; | 34 | font-weight: $font-semibold; |
53 | border-color: $video-watch-border-color; | 35 | flex-grow: 1; |
54 | border-width: 1px 0px; | 36 | } |
55 | border-style: solid; | ||
56 | 37 | ||
57 | .video-small-block { | 38 | .video-info-actions { |
58 | height: $video-watch-info-height; | 39 | .action-button { |
59 | display: flex; | 40 | @include peertube-button; |
60 | flex-direction: column; | ||
61 | justify-content: center; | ||
62 | text-align: center; | ||
63 | 41 | ||
64 | a { | 42 | font-size: 15px; |
65 | cursor: pointer; | 43 | font-weight: $font-semibold; |
66 | transition: color 0.3s; | 44 | color: #585858; |
67 | white-space: nowrap; | 45 | background-color: #E5E5E5; |
68 | overflow: hidden; | 46 | display: inline-block; |
69 | text-overflow: ellipsis; | 47 | padding: 0 10px 0 10px; |
70 | 48 | ||
71 | &, &:hover { | 49 | &:hover { |
72 | color: inherit; | 50 | background-color: #EFEFEF; |
73 | text-decoration:none; | 51 | } |
74 | } | 52 | } |
75 | 53 | ||
76 | &:hover { | 54 | .action-more { |
77 | color: #000 !important; | 55 | display: inline-block; |
78 | } | 56 | } |
79 | 57 | ||
80 | &:hover > .glyphicon { | 58 | .icon { |
81 | opacity: 1 !important; | 59 | display: inline-block; |
82 | } | 60 | background-repeat: no-repeat; |
83 | } | 61 | background-size: contain; |
62 | width: 21px; | ||
63 | height: 21px; | ||
64 | vertical-align: middle; | ||
65 | position: relative; | ||
66 | top: -2px; | ||
84 | 67 | ||
85 | .option .glyphicon { | 68 | &.icon-like { |
86 | font-size: 22px; | 69 | background-image: url('../../../assets/images/video/like.svg'); |
87 | color: inherit; | 70 | } |
88 | opacity: 0.15; | ||
89 | margin-bottom: 10px; | ||
90 | transition: opacity 0.3s; | ||
91 | } | ||
92 | 71 | ||
93 | .video-small-block-text { | 72 | &.icon-dislike { |
94 | font-size: 15px; | 73 | background-image: url('../../../assets/images/video/dislike.svg'); |
95 | font-weight: bold; | 74 | } |
96 | } | ||
97 | } | ||
98 | 75 | ||
99 | .video-small-block:not(:last-child) { | 76 | &.icon-share { |
100 | border-width: 0 1px 0 0; | 77 | background-image: url('../../../assets/images/video/share.svg'); |
101 | border-color: $video-watch-border-color; | 78 | } |
102 | border-style: solid; | ||
103 | } | ||
104 | 79 | ||
105 | .video-small-block-account, .video-small-block-more { | 80 | &.icon-more { |
106 | a.option { | 81 | background-image: url('../../../assets/images/video/more.svg'); |
107 | display: block; | 82 | } |
108 | |||
109 | .glyphicon { | ||
110 | display: block; | ||
111 | } | 83 | } |
112 | } | 84 | } |
113 | } | 85 | } |
114 | 86 | ||
115 | .video-small-block-share, .video-small-block-more { | 87 | .video-info-date-views { |
116 | a.option { | 88 | font-size: 16px; |
117 | display: block; | 89 | margin-bottom: 10px; |
118 | |||
119 | .glyphicon { | ||
120 | display: block; | ||
121 | } | ||
122 | } | ||
123 | } | 90 | } |
124 | 91 | ||
125 | .video-small-block-more .video-small-block-dropdown { | 92 | .video-info-channel { |
126 | position: relative; | 93 | font-weight: $font-semibold; |
127 | 94 | font-size: 15px; | |
128 | .dropdown-item .glyphicon { | ||
129 | margin-right: 5px; | ||
130 | } | ||
131 | } | 95 | } |
132 | 96 | ||
133 | .video-small-block-rating { | 97 | .video-info-by { |
134 | 98 | display: flex; | |
135 | .video-small-block-like { | 99 | align-items: center; |
136 | margin-bottom: 10px; | 100 | font-size: 13px; |
137 | } | ||
138 | |||
139 | .video-small-block-text { | ||
140 | vertical-align: top; | ||
141 | } | ||
142 | |||
143 | .glyphicon { | ||
144 | font-size: 18px; | ||
145 | margin: 0 10px 0 0; | ||
146 | opacity: 0.3; | ||
147 | } | ||
148 | |||
149 | .interactive { | ||
150 | cursor: pointer; | ||
151 | transition: opacity, color 0.3s; | ||
152 | 101 | ||
153 | &.activated, &:hover { | 102 | img { |
154 | opacity: 1; | 103 | width: 16px; |
155 | color: #000; | 104 | height: 16px; |
156 | } | 105 | margin-left: 3px; |
157 | } | 106 | } |
158 | } | 107 | } |
159 | } | ||
160 | |||
161 | .video-details { | ||
162 | margin-top: 30px; | ||
163 | 108 | ||
164 | .video-details-date-description { | 109 | .video-info-description { |
165 | padding-left: $video-watch-info-padding-left; | 110 | margin: 20px 0; |
111 | font-size: 15px; | ||
166 | 112 | ||
167 | .description-loading { | 113 | .description-loading { |
168 | display: inline-block; | 114 | display: inline-block; |
169 | } | 115 | } |
170 | 116 | ||
171 | .video-details-date { | 117 | .video-info-description-more { |
172 | font-weight: bold; | ||
173 | margin-bottom: 30px; | ||
174 | } | ||
175 | |||
176 | .video-details-description-more { | ||
177 | cursor: pointer; | 118 | cursor: pointer; |
178 | margin-top: 15px; | 119 | font-weight: $font-semibold; |
179 | font-weight: bold; | 120 | color: #585858; |
180 | color: #acaeb7; | 121 | font-size: 14px; |
181 | 122 | ||
182 | .glyphicon { | 123 | .glyphicon { |
183 | position: relative; | 124 | position: relative; |
@@ -186,109 +127,20 @@ | |||
186 | } | 127 | } |
187 | } | 128 | } |
188 | 129 | ||
189 | .video-details-attributes { | 130 | .video-attributes { |
190 | font-weight: bold; | 131 | .video-attribute { |
191 | font-size: 12px; | 132 | font-size: 13px; |
192 | 133 | display: block; | |
193 | .video-details-attribute { | 134 | margin-bottom: 12px; |
194 | display: flex; | ||
195 | |||
196 | .video-details-attribute-label { | ||
197 | color: $video-watch-info-color; | ||
198 | flex-basis: 60px; | ||
199 | flex-grow: 0; | ||
200 | flex-shrink: 0; | ||
201 | margin-right: 5px; | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | .video-details-tags { | ||
207 | display: flex; | ||
208 | flex-wrap: wrap; | ||
209 | |||
210 | a { | ||
211 | margin: 0 3px 3px 0; | ||
212 | font-size: 11px; | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | |||
217 | @media screen and (max-width: 800px) { | ||
218 | .video-name-views { | ||
219 | .video-name { | ||
220 | padding-left: 5px; | ||
221 | padding-right: 0px; | ||
222 | } | ||
223 | |||
224 | .video-views { | ||
225 | padding-left: 0px; | ||
226 | padding-right: 5px; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | .video-small-blocks { | ||
231 | a, .video-small-block-text { | ||
232 | font-size: 13px !important; | ||
233 | } | ||
234 | |||
235 | .glyphicon { | ||
236 | font-size: 18px !important; | ||
237 | } | ||
238 | |||
239 | .video-small-block-account { | ||
240 | padding-left: 10px; | ||
241 | padding-right: 10px; | ||
242 | } | ||
243 | } | ||
244 | |||
245 | .video-details { | ||
246 | .video-details-date-description { | ||
247 | padding-left: 10px; | ||
248 | font-size: 13px !important; | ||
249 | } | ||
250 | |||
251 | .video-details-attributes { | ||
252 | font-size: 11px !important; | ||
253 | 135 | ||
254 | .video-details-attribute-label { | 136 | .video-attribute-label { |
255 | width: 50px; | 137 | width: 86px; |
138 | display: inline-block; | ||
139 | color: #585858; | ||
140 | font-weight: $font-bold; | ||
256 | } | 141 | } |
257 | } | 142 | } |
258 | } | 143 | } |
259 | } | ||
260 | 144 | ||
261 | @media screen and (max-width: 500px) { | ||
262 | .video-name-views { | ||
263 | font-size: 16px !important; | ||
264 | } | ||
265 | |||
266 | // Keep the same hierarchy than max-width: 800px | ||
267 | .video-small-blocks { | ||
268 | a, .video-small-block-text { | ||
269 | font-size: 10px !important; | ||
270 | } | ||
271 | |||
272 | .video-small-block-account { | ||
273 | padding-left: 5px; | ||
274 | padding-right: 5px; | ||
275 | } | ||
276 | } | ||
277 | |||
278 | .video-details { | ||
279 | .video-details-date-description { | ||
280 | margin-bottom: 30px; | ||
281 | width: 100%; | ||
282 | |||
283 | .video-details-date { | ||
284 | margin-bottom: 15px; | ||
285 | } | ||
286 | } | ||
287 | |||
288 | .video-details-attributes { | ||
289 | padding-left: 10px; | ||
290 | padding-right: 10px; | ||
291 | } | ||
292 | } | ||
293 | } | 145 | } |
294 | } | 146 | } |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 48842602e..3c6951403 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -10,6 +10,8 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared' | |||
10 | import '../../../assets/player/peertube-videojs-plugin' | 10 | import '../../../assets/player/peertube-videojs-plugin' |
11 | import { AuthService, ConfirmService } from '../../core' | 11 | import { AuthService, ConfirmService } from '../../core' |
12 | import { VideoBlacklistService } from '../../shared' | 12 | import { VideoBlacklistService } from '../../shared' |
13 | import { Account } from '../../shared/account/account.model' | ||
14 | import { Video } from '../../shared/video/video.model' | ||
13 | import { MarkdownService } from '../shared' | 15 | import { MarkdownService } from '../shared' |
14 | import { VideoDownloadComponent } from './video-download.component' | 16 | import { VideoDownloadComponent } from './video-download.component' |
15 | import { VideoReportComponent } from './video-report.component' | 17 | import { VideoReportComponent } from './video-report.component' |
@@ -26,6 +28,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
26 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent | 28 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent |
27 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent | 29 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent |
28 | 30 | ||
31 | otherVideos: Video[] = [] | ||
32 | |||
29 | error = false | 33 | error = false |
30 | loading = false | 34 | loading = false |
31 | player: videojs.Player | 35 | player: videojs.Player |
@@ -57,6 +61,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
57 | ) {} | 61 | ) {} |
58 | 62 | ||
59 | ngOnInit () { | 63 | ngOnInit () { |
64 | this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt') | ||
65 | .subscribe( | ||
66 | data => this.otherVideos = data.videos, | ||
67 | |||
68 | err => console.error(err) | ||
69 | ) | ||
70 | |||
60 | this.paramsSub = this.route.params.subscribe(routeParams => { | 71 | this.paramsSub = this.route.params.subscribe(routeParams => { |
61 | let uuid = routeParams['uuid'] | 72 | let uuid = routeParams['uuid'] |
62 | this.videoService.getVideo(uuid).subscribe( | 73 | this.videoService.getVideo(uuid).subscribe( |
@@ -114,27 +125,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
114 | ) | 125 | ) |
115 | } | 126 | } |
116 | 127 | ||
117 | removeVideo (event: Event) { | ||
118 | event.preventDefault() | ||
119 | |||
120 | this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( | ||
121 | res => { | ||
122 | if (res === false) return | ||
123 | |||
124 | this.videoService.removeVideo(this.video.id) | ||
125 | .subscribe( | ||
126 | status => { | ||
127 | this.notificationsService.success('Success', `Video ${this.video.name} deleted.`) | ||
128 | // Go back to the video-list. | ||
129 | this.router.navigate(['/videos/list']) | ||
130 | }, | ||
131 | |||
132 | error => this.notificationsService.error('Error', error.text) | ||
133 | ) | ||
134 | } | ||
135 | ) | ||
136 | } | ||
137 | |||
138 | blacklistVideo (event: Event) { | 128 | blacklistVideo (event: Event) { |
139 | event.preventDefault() | 129 | event.preventDefault() |
140 | 130 | ||
@@ -165,7 +155,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
165 | } | 155 | } |
166 | 156 | ||
167 | showLessDescription () { | 157 | showLessDescription () { |
168 | |||
169 | this.updateVideoDescription(this.shortVideoDescription) | 158 | this.updateVideoDescription(this.shortVideoDescription) |
170 | this.completeDescriptionShown = false | 159 | this.completeDescriptionShown = false |
171 | } | 160 | } |
@@ -222,6 +211,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
222 | return this.video.isBlackistableBy(this.authService.getUser()) | 211 | return this.video.isBlackistableBy(this.authService.getUser()) |
223 | } | 212 | } |
224 | 213 | ||
214 | getAvatarPath () { | ||
215 | return Account.GET_ACCOUNT_AVATAR_PATH(this.video.account) | ||
216 | } | ||
217 | |||
218 | getVideoTags () { | ||
219 | if (!this.video || Array.isArray(this.video.tags) === false) return [] | ||
220 | |||
221 | return this.video.tags.join(', ') | ||
222 | } | ||
223 | |||
225 | private updateVideoDescription (description: string) { | 224 | private updateVideoDescription (description: string) { |
226 | this.video.description = description | 225 | this.video.description = description |
227 | this.setVideoDescriptionHTML() | 226 | this.setVideoDescriptionHTML() |
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts index 13024294e..5e7c7886c 100644 --- a/client/src/app/videos/video-list/index.ts +++ b/client/src/app/videos/video-list/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './video-recently-added.component' | 1 | export * from './video-recently-added.component' |
2 | export * from './video-trending.component' | 2 | export * from './video-trending.component' |
3 | export * from './video-search.component' | 3 | export * from './video-search.component' |
4 | export * from './shared' | ||
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts deleted file mode 100644 index 2778f2d9e..000000000 --- a/client/src/app/videos/video-list/shared/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-miniature.component' | ||
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 8c8d52ad9..4b14d1da8 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { SharedModule } from '../shared' | 2 | import { SharedModule } from '../shared' |
3 | import { VideoMiniatureComponent, VideoSearchComponent } from './video-list' | 3 | import { VideoSearchComponent } from './video-list' |
4 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 4 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
5 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 5 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
6 | import { VideosRoutingModule } from './videos-routing.module' | 6 | import { VideosRoutingModule } from './videos-routing.module' |
@@ -17,7 +17,6 @@ import { VideosComponent } from './videos.component' | |||
17 | 17 | ||
18 | VideoTrendingComponent, | 18 | VideoTrendingComponent, |
19 | VideoRecentlyAddedComponent, | 19 | VideoRecentlyAddedComponent, |
20 | VideoMiniatureComponent, | ||
21 | VideoSearchComponent | 20 | VideoSearchComponent |
22 | ], | 21 | ], |
23 | 22 | ||