# 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
--- /dev/null
+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'
+ }
+}
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'
declarations: [
LoaderComponent,
VideoThumbnailComponent,
+ VideoMiniatureComponent,
NumberFormatterPipe,
FromNowPipe
],
LoaderComponent,
VideoThumbnailComponent,
+ VideoMiniatureComponent,
NumberFormatterPipe,
FromNowPipe
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,
}
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)
}
}
>
<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>
+import { Account } from '../../../../../shared/models/accounts'
import { Video } from '../../shared/video/video.model'
import { AuthUser } from '../../core'
import {
} from '../../../../../shared'
export class VideoDetails extends Video implements VideoDetailsServerModel {
- account: string
+ accountName: string
by: string
createdAt: Date
updatedAt: Date
channel: VideoChannel
privacy: VideoPrivacy
privacyLabel: string
+ account: Account
constructor (hash: VideoDetailsServerModel) {
super(hash)
this.descriptionPath = hash.descriptionPath
this.files = hash.files
this.channel = hash.channel
+ this.account = hash.account
}
getAppropriateMagnetUri (actualDownloadSpeed = 0) {
}
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) {
}
isUpdatableBy (user: AuthUser) {
- return user && this.isLocal === true && user.username === this.account
+ return user && this.isLocal === true && user.username === this.accountName
}
}
--- /dev/null
+<div class="video-miniature">
+ <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
+
+ <div class="video-miniature-information">
+ <span class="video-miniature-name">
+ <a
+ class="video-miniature-name"
+ [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }"
+ >
+ {{ video.name }}
+ </a>
+ </span>
+
+ <span class="video-miniature-created-at-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
+ <span class="video-miniature-account">{{ video.by }}</span>
+ </div>
+</div>
--- /dev/null
+.video-miniature {
+ display: inline-block;
+ padding-right: 15px;
+ margin-bottom: 30px;
+ height: 175px;
+ vertical-align: top;
+
+ .video-miniature-information {
+ width: 200px;
+ margin-top: 2px;
+ line-height: normal;
+
+ .video-miniature-name {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: bold;
+ transition: color 0.2s;
+ font-size: 16px;
+ font-weight: $font-semibold;
+ color: #000;
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &.blur-filter {
+ filter: blur(3px);
+ padding-left: 4px;
+ }
+ }
+
+ .video-miniature-created-at-views {
+ display: block;
+ font-size: 13px;
+ }
+
+ .video-miniature-account {
+ font-size: 13px;
+ color: #585858;
+ }
+ }
+}
--- /dev/null
+import { Component, Input } from '@angular/core'
+import { User } from '../users'
+import { Video } from './video.model'
+
+@Component({
+ selector: 'my-video-miniature',
+ styleUrls: [ './video-miniature.component.scss' ],
+ templateUrl: './video-miniature.component.html'
+})
+export class VideoMiniatureComponent {
+ @Input() user: User
+ @Input() video: Video
+
+ isVideoNSFWForThisUser () {
+ return this.video.isVideoNSFWForUser(this.user)
+ }
+}
export interface VideoPagination {
currentPage: number
itemsPerPage: number
- totalItems: number
+ totalItems?: number
}
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
likes: number
dislikes: number
nsfw: boolean
+ account: Account
private static createByString (account: string, serverHost: string) {
return account + '@' + serverHost
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
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) {
-<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">
</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 class="col-xs-4 col-md-4 pull-right video-views">
- {{ video.views}} views
- </div>
- </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="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>
-
- <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 *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="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 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>
+
+ <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="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>
+ </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>
+ <div class="video-info-date-views">
+ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
+ </div>
- <span class="video-small-block-text">
- {{ video.dislikes }}
- </span>
- </div>
+ <div class="video-info-channel">
+ {{ video.channel.name }}
+ <!-- Here will be the subscribe button -->
</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-by">
+ By {{ video.by }}
+ <img [src]="getAvatarPath()" alt="Account avatar" />
+ </div>
- <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div>
+ <div class="video-info-description">
+ <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
- <div class="video-details-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()">
+ <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>
font-weight: bold;
}
-#torrent-info {
- font-size: 10px;
- margin-top: 10px;
- text-align: center;
-
- div {
- min-width: 60px;
- }
-}
-
-#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-bottom {
+ margin-top: 40px;
+ display: flex;
- .video-views {
- text-align: right;
- // Keep a symmetry with the video name
- padding-right: $video-watch-info-padding-left
- }
+ .video-info {
+ flex-grow: 1;
+ margin-right: 28px;
- }
+ .video-info-name-actions {
+ display: flex;
+ align-items: center;
- .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-info-name {
+ font-size: 27px;
+ font-weight: $font-semibold;
+ flex-grow: 1;
+ }
- .video-small-block {
- height: $video-watch-info-height;
- display: flex;
- flex-direction: column;
- justify-content: center;
- text-align: center;
+ .video-info-actions {
+ .action-button {
+ @include peertube-button;
- a {
- cursor: pointer;
- transition: color 0.3s;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ font-size: 15px;
+ font-weight: $font-semibold;
+ color: #585858;
+ background-color: #E5E5E5;
+ display: inline-block;
+ padding: 0 10px 0 10px;
- &, &:hover {
- color: inherit;
- text-decoration:none;
+ &:hover {
+ background-color: #EFEFEF;
+ }
}
- &:hover {
- color: #000 !important;
+ .action-more {
+ display: inline-block;
}
- &:hover > .glyphicon {
- opacity: 1 !important;
- }
- }
+ .icon {
+ display: inline-block;
+ background-repeat: no-repeat;
+ background-size: contain;
+ width: 21px;
+ height: 21px;
+ vertical-align: middle;
+ position: relative;
+ top: -2px;
- .option .glyphicon {
- font-size: 22px;
- color: inherit;
- opacity: 0.15;
- margin-bottom: 10px;
- transition: opacity 0.3s;
- }
+ &.icon-like {
+ background-image: url('../../../assets/images/video/like.svg');
+ }
- .video-small-block-text {
- font-size: 15px;
- font-weight: bold;
- }
- }
+ &.icon-dislike {
+ background-image: url('../../../assets/images/video/dislike.svg');
+ }
- .video-small-block:not(:last-child) {
- border-width: 0 1px 0 0;
- border-color: $video-watch-border-color;
- border-style: solid;
- }
+ &.icon-share {
+ background-image: url('../../../assets/images/video/share.svg');
+ }
- .video-small-block-account, .video-small-block-more {
- a.option {
- display: block;
-
- .glyphicon {
- display: block;
+ &.icon-more {
+ background-image: url('../../../assets/images/video/more.svg');
+ }
}
}
}
- .video-small-block-share, .video-small-block-more {
- a.option {
- display: block;
-
- .glyphicon {
- display: block;
- }
- }
+ .video-info-date-views {
+ font-size: 16px;
+ margin-bottom: 10px;
}
- .video-small-block-more .video-small-block-dropdown {
- position: relative;
-
- .dropdown-item .glyphicon {
- margin-right: 5px;
- }
+ .video-info-channel {
+ font-weight: $font-semibold;
+ font-size: 15px;
}
- .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;
+ .video-info-by {
+ display: flex;
+ align-items: center;
+ font-size: 13px;
- &.activated, &:hover {
- opacity: 1;
- color: #000;
- }
+ img {
+ width: 16px;
+ height: 16px;
+ margin-left: 3px;
}
}
- }
-
- .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;
}
}
- .video-details-attributes {
- font-weight: bold;
- font-size: 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-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-attributes {
+ .video-attribute {
+ font-size: 13px;
+ display: block;
+ margin-bottom: 12px;
- .video-details-attribute-label {
- width: 50px;
+ .video-attribute-label {
+ width: 86px;
+ display: inline-block;
+ color: #585858;
+ font-weight: $font-bold;
}
}
}
- }
- @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;
- }
- }
}
}
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'
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
+ otherVideos: Video[] = []
+
error = false
loading = false
player: videojs.Player
) {}
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(
)
}
- 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()
}
showLessDescription () {
-
this.updateVideoDescription(this.shortVideoDescription)
this.completeDescriptionShown = false
}
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()
export * from './video-recently-added.component'
export * from './video-trending.component'
export * from './video-search.component'
-export * from './shared'
+++ /dev/null
-export * from './video-miniature.component'
+++ /dev/null
-<div class="video-miniature">
- <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
-
- <div class="video-miniature-information">
- <span class="video-miniature-name">
- <a
- class="video-miniature-name"
- [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }"
- >
- {{ video.name }}
- </a>
- </span>
-
- <span class="video-miniature-created-at-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
- <span class="video-miniature-account">{{ video.by }}</span>
- </div>
-</div>
+++ /dev/null
-.video-miniature {
- display: inline-block;
- padding-right: 15px;
- margin-bottom: 30px;
- height: 175px;
- vertical-align: top;
-
- .video-miniature-information {
- width: 200px;
- margin-top: 2px;
- line-height: normal;
-
- .video-miniature-name {
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-weight: bold;
- transition: color 0.2s;
- font-size: 16px;
- font-weight: $font-semibold;
- color: #000;
-
- &:hover {
- text-decoration: none;
- }
-
- &.blur-filter {
- filter: blur(3px);
- padding-left: 4px;
- }
- }
-
- .video-miniature-created-at-views {
- display: block;
- font-size: 13px;
- }
-
- .video-miniature-account {
- font-size: 13px;
- color: #585858;
- }
- }
-}
+++ /dev/null
-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'
-
-@Component({
- selector: 'my-video-miniature',
- styleUrls: [ './video-miniature.component.scss' ],
- templateUrl: './video-miniature.component.html'
-})
-export class VideoMiniatureComponent {
- @Input() currentSort: SortField
- @Input() user: User
- @Input() video: Video
-
- isVideoNSFWForThisUser () {
- return this.video.isVideoNSFWForUser(this.user)
- }
-}
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'
VideoTrendingComponent,
VideoRecentlyAddedComponent,
- VideoMiniatureComponent,
VideoSearchComponent
],
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
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,
privacy: this.privacy,
descriptionPath: this.getDescriptionPath(),
channel: this.VideoChannel.toFormattedJSON(),
+ account: this.VideoChannel.Account.toFormattedJSON(),
files: []
}
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
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)
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
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
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
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)
}
})
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)
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')
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
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
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
// 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
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
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
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
const res = await getVideosList(server.url)
const video = res.body.data[ 0 ]
- expect(video.account)
+ expect(video.accountName)
.to
.equal('root')
videoId = video.id
.equal(1)
const video = res.body.data[ 0 ]
- expect(video.account)
+ expect(video.accountName)
.to
.equal('root')
})
id: number
uuid: string
name: string
- account: string
+ accountName: string
}
remoteVideo?: {
+import { Account } from '../accounts'
import { VideoChannel } from './video-channel.model'
import { VideoPrivacy } from './video-privacy.enum'
export interface Video {
id: number
uuid: string
- account: string
+ accountName: string
createdAt: Date | string
updatedAt: Date | string
categoryLabel: string
descriptionPath: string
channel: VideoChannel
files: VideoFile[]
+ account: Account
}