From b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2 Mon Sep 17 00:00:00 2001 From: Chocobozzz <florian.bigard@gmail.com> Date: Wed, 6 Dec 2017 17:15:59 +0100 Subject: [PATCH] Begin video watch design --- CREDITS.md | 9 +- .../src/app/shared/account/account.model.ts | 20 ++ client/src/app/shared/shared.module.ts | 3 + client/src/app/shared/users/user.model.ts | 6 +- .../app/shared/video/abstract-video-list.html | 2 +- .../app/shared/video/video-details.model.ts | 9 +- .../video}/video-miniature.component.html | 0 .../video}/video-miniature.component.scss | 0 .../video}/video-miniature.component.ts | 6 +- .../shared/video/video-pagination.model.ts | 2 +- client/src/app/shared/video/video.model.ts | 8 +- .../+video-watch/video-watch.component.html | 223 ++++++------ .../+video-watch/video-watch.component.scss | 324 +++++------------- .../+video-watch/video-watch.component.ts | 43 ++- client/src/app/videos/video-list/index.ts | 1 - .../src/app/videos/video-list/shared/index.ts | 1 - client/src/app/videos/videos.module.ts | 3 +- client/src/assets/images/video/dislike.svg | 14 + client/src/assets/images/video/like.svg | 15 + client/src/assets/images/video/more.svg | 11 + client/src/assets/images/video/share.svg | 16 + server/models/video/video.ts | 3 +- server/tests/api/follows.ts | 2 +- server/tests/api/multiple-servers.ts | 11 +- server/tests/api/services.ts | 4 +- server/tests/api/single-server.ts | 15 +- server/tests/api/users.ts | 4 +- server/tests/utils/servers.ts | 2 +- shared/models/videos/video.model.ts | 4 +- 29 files changed, 330 insertions(+), 431 deletions(-) create mode 100644 client/src/app/shared/account/account.model.ts rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.html (100%) rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.scss (100%) rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.ts (65%) delete mode 100644 client/src/app/videos/video-list/shared/index.ts create mode 100644 client/src/assets/images/video/dislike.svg create mode 100644 client/src/assets/images/video/like.svg create mode 100644 client/src/assets/images/video/more.svg create mode 100644 client/src/assets/images/video/share.svg diff --git a/CREDITS.md b/CREDITS.md index a7b2b5568..65017bbc1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -8,14 +8,9 @@ # Design -Inspirations from: +By [Olivier Massain](https://twitter.com/omassain) - * [Aurélien Salomon](https://dribbble.com/shots/1338727-Youtube-Redesign) - * [Wojciech Zieliński](https://dribbble.com/shots/3000315-youtube-concept) - -Video.js theme: - - * [zanechua](https://github.com/zanechua/videojs-sublime-inspired-skin) +Icons from [Robbie Pearce](https://robbiepearce.com/softies/) # Fonts 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 @@ +import { Account as ServerAccount } from '../../../../../shared/models/accounts/account.model' +import { Avatar } from '../../../../../shared/models/avatars/avatar.model' + +export class Account implements ServerAccount { + id: number + uuid: string + name: string + host: string + followingCount: number + followersCount: number + createdAt: Date + updatedAt: Date + avatar: Avatar + + static GET_ACCOUNT_AVATAR_PATH (account: Account) { + if (account && account.avatar) return account.avatar.path + + return API_URL + '/client/assets/images/default-avatar.png' + } +} 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' import { UserService } from './users' import { VideoAbuseService } from './video-abuse' import { VideoBlacklistService } from './video-blacklist' +import { VideoMiniatureComponent } from './video/video-miniature.component' import { VideoThumbnailComponent } from './video/video-thumbnail.component' import { VideoService } from './video/video.service' @@ -44,6 +45,7 @@ import { VideoService } from './video/video.service' declarations: [ LoaderComponent, VideoThumbnailComponent, + VideoMiniatureComponent, NumberFormatterPipe, FromNowPipe ], @@ -66,6 +68,7 @@ import { VideoService } from './video/video.service' LoaderComponent, VideoThumbnailComponent, + VideoMiniatureComponent, NumberFormatterPipe, 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 @@ import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' -import { Account } from '../../../../../shared/models/accounts' +import { Account } from '../account/account.model' export type UserConstructorHash = { id: number, @@ -52,8 +52,6 @@ export class User implements UserServerModel { } getAvatarPath () { - if (this.account && this.account.avatar) return this.account.avatar.path - - return API_URL + '/client/assets/images/default-avatar.png' + return Account.GET_ACCOUNT_AVATAR_PATH(this.account) } } 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 @@ > <my-video-miniature class="ng-animate" - *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" + *ngFor="let video of videos" [video]="video" [user]="user" > </my-video-miniature> </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 @@ +import { Account } from '../../../../../shared/models/accounts' import { Video } from '../../shared/video/video.model' import { AuthUser } from '../../core' import { @@ -10,7 +11,7 @@ import { } from '../../../../../shared' export class VideoDetails extends Video implements VideoDetailsServerModel { - account: string + accountName: string by: string createdAt: Date updatedAt: Date @@ -44,6 +45,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { channel: VideoChannel privacy: VideoPrivacy privacyLabel: string + account: Account constructor (hash: VideoDetailsServerModel) { super(hash) @@ -53,6 +55,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { this.descriptionPath = hash.descriptionPath this.files = hash.files this.channel = hash.channel + this.account = hash.account } getAppropriateMagnetUri (actualDownloadSpeed = 0) { @@ -71,7 +74,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { } isRemovableBy (user: AuthUser) { - return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) + return user && this.isLocal === true && (this.accountName === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) } isBlackistableBy (user: AuthUser) { @@ -79,6 +82,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { } isUpdatableBy (user: AuthUser) { - return user && this.isLocal === true && user.username === this.account + return user && this.isLocal === true && user.username === this.accountName } } diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html similarity index 100% rename from client/src/app/videos/video-list/shared/video-miniature.component.html rename to 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 similarity index 100% rename from client/src/app/videos/video-list/shared/video-miniature.component.scss rename to 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 similarity index 65% rename from client/src/app/videos/video-list/shared/video-miniature.component.ts rename to 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 @@ import { Component, Input } from '@angular/core' -import { User } from '../../../shared' -import { SortField } from '../../../shared/video/sort-field.type' -import { Video } from '../../../shared/video/video.model' +import { User } from '../users' +import { Video } from './video.model' @Component({ selector: 'my-video-miniature', @@ -9,7 +8,6 @@ import { Video } from '../../../shared/video/video.model' templateUrl: './video-miniature.component.html' }) export class VideoMiniatureComponent { - @Input() currentSort: SortField @Input() user: User @Input() video: Video 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 @@ export interface VideoPagination { currentPage: number itemsPerPage: number - totalItems: number + totalItems?: number } 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 @@ import { Video as VideoServerModel } from '../../../../../shared' import { User } from '../' +import { Account } from '../../../../../shared/models/accounts' export class Video implements VideoServerModel { - account: string + accountName: string by: string createdAt: Date updatedAt: Date @@ -31,6 +32,7 @@ export class Video implements VideoServerModel { likes: number dislikes: number nsfw: boolean + account: Account private static createByString (account: string, serverHost: string) { return account + '@' + serverHost @@ -52,7 +54,7 @@ export class Video implements VideoServerModel { absoluteAPIUrl = window.location.origin } - this.account = hash.account + this.accountName = hash.accountName this.createdAt = new Date(hash.createdAt.toString()) this.categoryLabel = hash.categoryLabel this.category = hash.category @@ -80,7 +82,7 @@ export class Video implements VideoServerModel { this.dislikes = hash.dislikes this.nsfw = hash.nsfw - this.by = Video.createByString(hash.account, hash.serverHost) + this.by = Video.createByString(hash.accountName, hash.serverHost) } 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 @@ -<div *ngIf="error" class="row"> - <div class="alert alert-danger"> - The video load seems to be abnormally long. - <ul> - <li>Maybe the server {{ video.serverHost }} is down :(</li> - <li> - If not, you can report an issue on - <a href="https://github.com/Chocobozzz/PeerTube/issues" title="Report an issue"> - https://github.com/Chocobozzz/PeerTube/issues - </a> - </li> - </ul> - </div> -</div> - <div class="row"> <!-- We need the video container for videojs so we just hide it --> <div [hidden]="videoNotFound" id="video-container"> @@ -23,167 +8,153 @@ </div> <!-- Video information --> -<div *ngIf="video !== null" id="video-info"> - <div class="row video-name-views"> - <div class="col-xs-8 col-md-8 video-name"> - {{ video.name }} - </div> +<div *ngIf="video" class="margin-content video-bottom"> + <div class="video-info"> + <div class="video-info-name-actions"> + <div class="video-info-name">{{ video.name }}</div> - <div class="col-xs-4 col-md-4 pull-right video-views"> - {{ video.views}} views - </div> - </div> + <div class="video-info-actions"> + <div class="action-button"> + <span + class="icon icon-like" title="Like this video" + [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()" + ></span> + </div> - <div class="row video-small-blocks"> - <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-account"> - <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'account', search: video.account }]"> - <span class="glyphicon glyphicon-user"></span> - <span class="video-small-block-text">{{ video.by }}</span> - </a> - </div> + <div class="action-button"> + <span + class="icon icon-dislike" title="Dislike this video" + [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()" + ></span> + </div> - <div class="col-xs-2 col-md-3 video-small-block video-small-block-share"> - <a class="option" (click)="showShareModal()" title="Share the video"> - <span class="glyphicon glyphicon-share"></span> - <span class="hidden-xs video-small-block-text">Share</span> - </a> - </div> + <div (click)="showShareModal()" class="action-button"> + <span class="icon icon-share"></span> + Share + </div> - <div class="col-xs-2 col-md-3 video-small-block video-small-block-more"> - <div class="video-small-block-dropdown" dropdown dropup="true" placement="right"> - <a class="option" title="Access to more options" dropdownToggle> - <span class="glyphicon glyphicon-option-horizontal"></span> - <span class="hidden-xs video-small-block-text">More</span> - </a> + <div class="action-more" dropdown dropup="true" placement="right"> + <div class="action-button" dropdownToggle> + <span class="icon icon-more"></span> + </div> - <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> - <li *ngIf="canUserUpdateVideo()" role="menuitem"> - <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]"> - <span class="glyphicon glyphicon-pencil"></span> Update - </a> - </li> + <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> + <li *ngIf="canUserUpdateVideo()" role="menuitem"> + <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]"> + <span class="glyphicon glyphicon-pencil"></span> Update + </a> + </li> - <li role="menuitem"> - <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)"> - <span class="glyphicon glyphicon-download-alt"></span> Download - </a> - </li> + <li role="menuitem"> + <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)"> + <span class="glyphicon glyphicon-download-alt"></span> Download + </a> + </li> - <li *ngIf="isUserLoggedIn()" role="menuitem"> - <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> - <span class="glyphicon glyphicon-alert"></span> Report - </a> - </li> + <li *ngIf="isUserLoggedIn()" role="menuitem"> + <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> + <span class="glyphicon glyphicon-alert"></span> Report + </a> + </li> - <li *ngIf="isVideoRemovable()" role="menuitem"> - <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> - <span class="glyphicon glyphicon-remove"></span> Delete - </a> - </li> + <li *ngIf="isVideoRemovable()" role="menuitem"> + <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> + <span class="glyphicon glyphicon-remove"></span> Delete + </a> + </li> - <li *ngIf="isVideoBlacklistable()" role="menuitem"> - <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> - <span class="glyphicon glyphicon-eye-close"></span> Blacklist - </a> - </li> - </ul> + <li *ngIf="isVideoBlacklistable()" role="menuitem"> + <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> + <span class="glyphicon glyphicon-eye-close"></span> Blacklist + </a> + </li> + </ul> + </div> </div> </div> - <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating"> - <div class="video-small-block-like"> - <span - class="glyphicon glyphicon-thumbs-up" title="Like this video" - [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()" - ></span> - - <span class="video-small-block-text"> - {{ video.likes }} - </span> - </div> - - <div class="video-small-block-dislike"> - <span - class="glyphicon glyphicon-thumbs-down" title="Dislike this video" - [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()" - ></span> - - <span class="video-small-block-text"> - {{ video.dislikes }} - </span> - </div> + <div class="video-info-date-views"> + {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views </div> - </div> - <div class="row video-details"> - <div class="video-details-date-description col-xs-8 col-md-9"> - <div class="video-details-date"> - Published on {{ video.createdAt | date:'short' }} - </div> + <div class="video-info-channel"> + {{ video.channel.name }} + <!-- Here will be the subscribe button --> + </div> - <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div> + <div class="video-info-by"> + By {{ video.by }} + <img [src]="getAvatarPath()" alt="Account avatar" /> + </div> - <div class="video-details-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()"> + <div class="video-info-description"> + <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div> + + <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()"> Show more <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader> </div> - <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-details-description-more"> + <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> Show less <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> </div> </div> - <div class="video-details-attributes col-xs-4 col-md-3"> - <div class="video-details-attribute"> - <span class="video-details-attribute-label"> - Privacy: + <div class="video-attributes"> + <div class="video-attribute"> + <span class="video-attribute-label"> + Privacy </span> - <span class="video-details-attribute-value"> + <span class="video-attribute-value"> {{ video.privacyLabel }} </span> </div> - <div class="video-details-attribute"> - <span class="video-details-attribute-label"> - Category: + <div class="video-attribute"> + <span class="video-attribute-label"> + Category </span> - <span class="video-details-attribute-value"> + <span class="video-attribute-value"> {{ video.categoryLabel }} </span> </div> - <div class="video-details-attribute"> - <span class="video-details-attribute-label"> - Licence: + <div class="video-attribute"> + <span class="video-attribute-label"> + Licence </span> - <span class="video-details-attribute-value"> + <span class="video-attribute-value"> {{ video.licenceLabel }} </span> </div> - <div class="video-details-attribute"> - <span class="video-details-attribute-label"> - Language: + <div class="video-attribute"> + <span class="video-attribute-label"> + Language </span> - <span class="video-details-attribute-value"> + <span class="video-attribute-value"> {{ video.languageLabel }} </span> </div> - <div class="video-details-attribute"> - <span class="video-details-attribute-label"> - Tags: + <div class="video-attribute"> + <span class="video-attribute-label"> + Tags </span> - <div class="video-details-tags"> - <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary"> - {{ tag }} - </a> - </div> + <span class="video-attribute-value"> + {{ getVideoTags() }} + </span> </div> + </div> + </div> + + <div class="other-videos"> + <div *ngFor="let video of otherVideos"> + <my-video-miniature [video]="video" [user]="user"></my-video-miniature> </div> </div> </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 @@ font-weight: bold; } -#torrent-info { - font-size: 10px; - margin-top: 10px; - text-align: center; +.video-bottom { + margin-top: 40px; + display: flex; - div { - min-width: 60px; - } -} + .video-info { + flex-grow: 1; + margin-right: 28px; -#video-info { - .video-name-views { - font-weight: bold; - font-size: 18px; - min-height: $video-watch-title-height; - display: flex; - align-items: center; - - .video-name { - padding-left: $video-watch-info-padding-left; - } - - .video-views { - text-align: right; - // Keep a symmetry with the video name - padding-right: $video-watch-info-padding-left - } - - } - - .video-small-blocks { - height: $video-watch-info-height; - color: $video-watch-info-color; - border-color: $video-watch-border-color; - border-width: 1px 0px; - border-style: solid; - - .video-small-block { - height: $video-watch-info-height; + .video-info-name-actions { display: flex; - flex-direction: column; - justify-content: center; - text-align: center; + align-items: center; - a { - cursor: pointer; - transition: color 0.3s; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &, &:hover { - color: inherit; - text-decoration:none; - } - - &:hover { - color: #000 !important; - } - - &:hover > .glyphicon { - opacity: 1 !important; - } + .video-info-name { + font-size: 27px; + font-weight: $font-semibold; + flex-grow: 1; } - .option .glyphicon { - font-size: 22px; - color: inherit; - opacity: 0.15; - margin-bottom: 10px; - transition: opacity 0.3s; - } + .video-info-actions { + .action-button { + @include peertube-button; - .video-small-block-text { - font-size: 15px; - font-weight: bold; - } - } + font-size: 15px; + font-weight: $font-semibold; + color: #585858; + background-color: #E5E5E5; + display: inline-block; + padding: 0 10px 0 10px; - .video-small-block:not(:last-child) { - border-width: 0 1px 0 0; - border-color: $video-watch-border-color; - border-style: solid; - } + &:hover { + background-color: #EFEFEF; + } + } - .video-small-block-account, .video-small-block-more { - a.option { - display: block; + .action-more { + display: inline-block; + } - .glyphicon { - display: block; + .icon { + display: inline-block; + background-repeat: no-repeat; + background-size: contain; + width: 21px; + height: 21px; + vertical-align: middle; + position: relative; + top: -2px; + + &.icon-like { + background-image: url('../../../assets/images/video/like.svg'); + } + + &.icon-dislike { + background-image: url('../../../assets/images/video/dislike.svg'); + } + + &.icon-share { + background-image: url('../../../assets/images/video/share.svg'); + } + + &.icon-more { + background-image: url('../../../assets/images/video/more.svg'); + } } } } - .video-small-block-share, .video-small-block-more { - a.option { - display: block; + .video-info-date-views { + font-size: 16px; + margin-bottom: 10px; + } - .glyphicon { - display: block; - } + .video-info-channel { + font-weight: $font-semibold; + font-size: 15px; + } + + .video-info-by { + display: flex; + align-items: center; + font-size: 13px; + + img { + width: 16px; + height: 16px; + margin-left: 3px; } } - .video-small-block-more .video-small-block-dropdown { - position: relative; - - .dropdown-item .glyphicon { - margin-right: 5px; - } - } - - .video-small-block-rating { - - .video-small-block-like { - margin-bottom: 10px; - } - - .video-small-block-text { - vertical-align: top; - } - - .glyphicon { - font-size: 18px; - margin: 0 10px 0 0; - opacity: 0.3; - } - - .interactive { - cursor: pointer; - transition: opacity, color 0.3s; - - &.activated, &:hover { - opacity: 1; - color: #000; - } - } - } - } - - .video-details { - margin-top: 30px; - - .video-details-date-description { - padding-left: $video-watch-info-padding-left; + .video-info-description { + margin: 20px 0; + font-size: 15px; .description-loading { display: inline-block; } - .video-details-date { - font-weight: bold; - margin-bottom: 30px; - } - - .video-details-description-more { + .video-info-description-more { cursor: pointer; - margin-top: 15px; - font-weight: bold; - color: #acaeb7; + font-weight: $font-semibold; + color: #585858; + font-size: 14px; .glyphicon { position: relative; @@ -186,109 +127,20 @@ } } - .video-details-attributes { - font-weight: bold; - font-size: 12px; + .video-attributes { + .video-attribute { + font-size: 13px; + display: block; + margin-bottom: 12px; - .video-details-attribute { - display: flex; - - .video-details-attribute-label { - color: $video-watch-info-color; - flex-basis: 60px; - flex-grow: 0; - flex-shrink: 0; - margin-right: 5px; + .video-attribute-label { + width: 86px; + display: inline-block; + color: #585858; + font-weight: $font-bold; } } } - .video-details-tags { - display: flex; - flex-wrap: wrap; - - a { - margin: 0 3px 3px 0; - font-size: 11px; - } - } - } - - @media screen and (max-width: 800px) { - .video-name-views { - .video-name { - padding-left: 5px; - padding-right: 0px; - } - - .video-views { - padding-left: 0px; - padding-right: 5px; - } - } - - .video-small-blocks { - a, .video-small-block-text { - font-size: 13px !important; - } - - .glyphicon { - font-size: 18px !important; - } - - .video-small-block-account { - padding-left: 10px; - padding-right: 10px; - } - } - - .video-details { - .video-details-date-description { - padding-left: 10px; - font-size: 13px !important; - } - - .video-details-attributes { - font-size: 11px !important; - - .video-details-attribute-label { - width: 50px; - } - } - } - } - - @media screen and (max-width: 500px) { - .video-name-views { - font-size: 16px !important; - } - - // Keep the same hierarchy than max-width: 800px - .video-small-blocks { - a, .video-small-block-text { - font-size: 10px !important; - } - - .video-small-block-account { - padding-left: 5px; - padding-right: 5px; - } - } - - .video-details { - .video-details-date-description { - margin-bottom: 30px; - width: 100%; - - .video-details-date { - margin-bottom: 15px; - } - } - - .video-details-attributes { - padding-left: 10px; - padding-right: 10px; - } - } } } 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' import '../../../assets/player/peertube-videojs-plugin' import { AuthService, ConfirmService } from '../../core' import { VideoBlacklistService } from '../../shared' +import { Account } from '../../shared/account/account.model' +import { Video } from '../../shared/video/video.model' import { MarkdownService } from '../shared' import { VideoDownloadComponent } from './video-download.component' import { VideoReportComponent } from './video-report.component' @@ -26,6 +28,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { @ViewChild('videoShareModal') videoShareModal: VideoShareComponent @ViewChild('videoReportModal') videoReportModal: VideoReportComponent + otherVideos: Video[] = [] + error = false loading = false player: videojs.Player @@ -57,6 +61,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { ) {} ngOnInit () { + this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt') + .subscribe( + data => this.otherVideos = data.videos, + + err => console.error(err) + ) + this.paramsSub = this.route.params.subscribe(routeParams => { let uuid = routeParams['uuid'] this.videoService.getVideo(uuid).subscribe( @@ -114,27 +125,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { ) } - removeVideo (event: Event) { - event.preventDefault() - - this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( - res => { - if (res === false) return - - this.videoService.removeVideo(this.video.id) - .subscribe( - status => { - this.notificationsService.success('Success', `Video ${this.video.name} deleted.`) - // Go back to the video-list. - this.router.navigate(['/videos/list']) - }, - - error => this.notificationsService.error('Error', error.text) - ) - } - ) - } - blacklistVideo (event: Event) { event.preventDefault() @@ -165,7 +155,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } showLessDescription () { - this.updateVideoDescription(this.shortVideoDescription) this.completeDescriptionShown = false } @@ -222,6 +211,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return this.video.isBlackistableBy(this.authService.getUser()) } + getAvatarPath () { + return Account.GET_ACCOUNT_AVATAR_PATH(this.video.account) + } + + getVideoTags () { + if (!this.video || Array.isArray(this.video.tags) === false) return [] + + return this.video.tags.join(', ') + } + private updateVideoDescription (description: string) { this.video.description = description 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 @@ export * from './video-recently-added.component' export * from './video-trending.component' export * from './video-search.component' -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 @@ -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 @@ import { NgModule } from '@angular/core' import { SharedModule } from '../shared' -import { VideoMiniatureComponent, VideoSearchComponent } from './video-list' +import { VideoSearchComponent } from './video-list' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoTrendingComponent } from './video-list/video-trending.component' import { VideosRoutingModule } from './videos-routing.module' @@ -17,7 +17,6 @@ import { VideosComponent } from './videos.component' VideoTrendingComponent, VideoRecentlyAddedComponent, - VideoMiniatureComponent, VideoSearchComponent ], diff --git a/client/src/assets/images/video/dislike.svg b/client/src/assets/images/video/dislike.svg new file mode 100644 index 000000000..56a7908fb --- /dev/null +++ b/client/src/assets/images/video/dislike.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round"> + <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#585858" stroke-width="2"> + <g id="Extras" transform="translate(48.000000, 1046.000000)"> + <g id="thumbs-down" transform="translate(704.000000, 44.000000)"> + <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path> + <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path> + </g> + </g> + </g> + </g> +</svg> diff --git a/client/src/assets/images/video/like.svg b/client/src/assets/images/video/like.svg new file mode 100644 index 000000000..5ef6c7b31 --- /dev/null +++ b/client/src/assets/images/video/like.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch --> + <title>thumbs-up</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round"> + <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#585858" stroke-width="2"> + <g id="256" transform="translate(708.000000, 643.000000)"> + <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path> + <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path> + </g> + </g> + </g> +</svg> diff --git a/client/src/assets/images/video/more.svg b/client/src/assets/images/video/more.svg new file mode 100644 index 000000000..dea392136 --- /dev/null +++ b/client/src/assets/images/video/more.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Artboard-4" transform="translate(-444.000000, -115.000000)" fill="#585858"> + <g id="10" transform="translate(444.000000, 115.000000)"> + <path d="M10,12 C10,10.8954305 10.8877296,10 12,10 C13.1045695,10 14,10.8877296 14,12 C14,13.1045695 13.1122704,14 12,14 C10.8954305,14 10,13.1122704 10,12 Z M17,12 C17,10.8954305 17.8877296,10 19,10 C20.1045695,10 21,10.8877296 21,12 C21,13.1045695 20.1122704,14 19,14 C17.8954305,14 17,13.1122704 17,12 Z M3,12 C3,10.8954305 3.88772964,10 5,10 C6.1045695,10 7,10.8877296 7,12 C7,13.1045695 6.11227036,14 5,14 C3.8954305,14 3,13.1122704 3,12 Z" id="Combined-Shape"></path> + </g> + </g> + </g> +</svg> diff --git a/client/src/assets/images/video/share.svg b/client/src/assets/images/video/share.svg new file mode 100644 index 000000000..da0f43e81 --- /dev/null +++ b/client/src/assets/images/video/share.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch --> + <title>share</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round"> + <g id="Artboard-4" transform="translate(-312.000000, -203.000000)" stroke="#585858" stroke-width="2"> + <g id="47" transform="translate(312.000000, 203.000000)"> + <path d="M20,15 L20,18.0026083 C20,19.1057373 19.1073772,20 18.0049107,20 L5.99508929,20 C4.8932319,20 4,19.1073772 4,18.0049107 L4,5.99508929 C4,4.8932319 4.89585781,4 5.9973917,4 L9,4" id="Rectangle-460"></path> + <polyline id="Path-93" stroke-linejoin="round" points="13 4 20.0207973 4 20.0207973 11.0191059"></polyline> + <path d="M19,5 L12,12" id="Path-94" stroke-linejoin="round"></path> + </g> + </g> + </g> +</svg> diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 4dce8e2fc..60023bc8c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -486,7 +486,7 @@ toFormattedJSON = function (this: VideoInstance) { description: this.getTruncatedDescription(), serverHost, isLocal: this.isOwned(), - account: this.VideoChannel.Account.name, + accountName: this.VideoChannel.Account.name, duration: this.duration, views: this.views, likes: this.likes, @@ -514,6 +514,7 @@ toFormattedDetailsJSON = function (this: VideoInstance) { privacy: this.privacy, descriptionPath: this.getDescriptionPath(), channel: this.VideoChannel.toFormattedJSON(), + account: this.VideoChannel.Account.toFormattedJSON(), files: [] } diff --git a/server/tests/api/follows.ts b/server/tests/api/follows.ts index aadae3cce..dcb4c8bd9 100644 --- a/server/tests/api/follows.ts +++ b/server/tests/api/follows.ts @@ -227,7 +227,7 @@ describe('Test follows', function () { expect(videoDetails.nsfw).to.be.ok expect(videoDetails.description).to.equal('my super description') expect(videoDetails.serverHost).to.equal('localhost:9003') - expect(videoDetails.account).to.equal('root') + expect(videoDetails.accountName).to.equal('root') expect(videoDetails.likes).to.equal(1) expect(videoDetails.dislikes).to.equal(1) expect(videoDetails.isLocal).to.be.false diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts index c80ded862..c7f19f261 100644 --- a/server/tests/api/multiple-servers.ts +++ b/server/tests/api/multiple-servers.ts @@ -111,13 +111,14 @@ describe('Test multiple servers', function () { expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) expect(dateIsValid(video.createdAt)).to.be.true expect(dateIsValid(video.updatedAt)).to.be.true - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') const res2 = await getVideo(server.url, video.uuid) const videoDetails = res2.body expect(videoDetails.channel.name).to.equal('my channel') expect(videoDetails.channel.description).to.equal('super channel') + expect(videoDetails.account.name).to.equal('root') expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true expect(videoDetails.files).to.have.lengthOf(1) @@ -201,7 +202,7 @@ describe('Test multiple servers', function () { expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) expect(dateIsValid(video.createdAt)).to.be.true expect(dateIsValid(video.updatedAt)).to.be.true - expect(video.account).to.equal('user1') + expect(video.accountName).to.equal('user1') if (server.url !== 'http://localhost:9002') { expect(video.isLocal).to.be.false @@ -316,7 +317,7 @@ describe('Test multiple servers', function () { expect(video1.serverHost).to.equal('localhost:9003') expect(video1.duration).to.equal(5) expect(video1.tags).to.deep.equal([ 'tag1p3' ]) - expect(video1.account).to.equal('root') + expect(video1.accountName).to.equal('root') expect(dateIsValid(video1.createdAt)).to.be.true expect(dateIsValid(video1.updatedAt)).to.be.true @@ -342,7 +343,7 @@ describe('Test multiple servers', function () { expect(video2.serverHost).to.equal('localhost:9003') expect(video2.duration).to.equal(5) expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) - expect(video2.account).to.equal('root') + expect(video2.accountName).to.equal('root') expect(dateIsValid(video2.createdAt)).to.be.true expect(dateIsValid(video2.updatedAt)).to.be.true @@ -690,7 +691,7 @@ describe('Test multiple servers', function () { expect(baseVideo.licence).to.equal(video.licence) expect(baseVideo.category).to.equal(video.category) expect(baseVideo.nsfw).to.equal(video.nsfw) - expect(baseVideo.account).to.equal(video.account) + expect(baseVideo.accountName).to.equal(video.accountName) expect(baseVideo.tags).to.deep.equal(video.tags) } }) diff --git a/server/tests/api/services.ts b/server/tests/api/services.ts index 8d96ccc5e..4d480c305 100644 --- a/server/tests/api/services.ts +++ b/server/tests/api/services.ts @@ -46,7 +46,7 @@ describe('Test services', function () { expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(server.video.name) - expect(res.body.author_name).to.equal(server.video.account) + expect(res.body.author_name).to.equal(server.video.accountName) expect(res.body.width).to.equal(560) expect(res.body.height).to.equal(315) expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) @@ -66,7 +66,7 @@ describe('Test services', function () { expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(server.video.name) - expect(res.body.author_name).to.equal(server.video.account) + expect(res.body.author_name).to.equal(server.video.accountName) expect(res.body.height).to.equal(50) expect(res.body.width).to.equal(50) expect(res.body).to.not.have.property('thumbnail_url') diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index fe192d391..d7e9ad41f 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -127,7 +127,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -176,7 +176,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -243,7 +243,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -298,7 +298,7 @@ describe('Test a single server', function () { // expect(video.nsfw).to.be.ok // expect(video.description).to.equal('my super description') // expect(video.serverHost).to.equal('localhost:9001') - // expect(video.account).to.equal('root') + // expect(video.accountName).to.equal('root') // expect(video.isLocal).to.be.true // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) // expect(dateIsValid(video.createdAt)).to.be.true @@ -563,7 +563,8 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description updated') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') + expect(video.account.name).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -612,7 +613,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description updated') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -652,7 +653,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('hello everybody') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) expect(dateIsValid(video.createdAt)).to.be.true diff --git a/server/tests/api/users.ts b/server/tests/api/users.ts index 33646e84f..5066e73fc 100644 --- a/server/tests/api/users.ts +++ b/server/tests/api/users.ts @@ -117,7 +117,7 @@ describe('Test users', function () { const res = await getVideosList(server.url) const video = res.body.data[ 0 ] - expect(video.account) + expect(video.accountName) .to .equal('root') videoId = video.id @@ -487,7 +487,7 @@ describe('Test users', function () { .equal(1) const video = res.body.data[ 0 ] - expect(video.account) + expect(video.accountName) .to .equal('root') }) diff --git a/server/tests/utils/servers.ts b/server/tests/utils/servers.ts index faa2f19ff..8340fbc18 100644 --- a/server/tests/utils/servers.ts +++ b/server/tests/utils/servers.ts @@ -24,7 +24,7 @@ interface ServerInfo { id: number uuid: string name: string - account: string + accountName: string } remoteVideo?: { diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 08b29425c..dc12a05d9 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts @@ -1,3 +1,4 @@ +import { Account } from '../accounts' import { VideoChannel } from './video-channel.model' import { VideoPrivacy } from './video-privacy.enum' @@ -13,7 +14,7 @@ export interface VideoFile { export interface Video { id: number uuid: string - account: string + accountName: string createdAt: Date | string updatedAt: Date | string categoryLabel: string @@ -43,4 +44,5 @@ export interface VideoDetails extends Video { descriptionPath: string channel: VideoChannel files: VideoFile[] + account: Account }