diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html index 28ea7a857..e2d09a36d 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html @@ -1,16 +1,61 @@ -
No videos in this playlist.
+
No videos in this playlist.
- +
{{ video.playlistElement.position }}
+ +
-
{{ video.playlistElement.position }}
- - {{ video.name }} - {{ video.name }} + {{ formatTimestamp(video)}} +
+ +
+ + +
+ + +
+
+ + + +
+ +
+ + + +
+ + +
+ + + Delete from {{playlist?.displayName}} + +
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss index 5e6774739..3be10078e 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss @@ -1,2 +1,96 @@ @import '_variables'; @import '_mixins'; +@import '_miniature'; + +.videos { + .video { + display: flex; + align-items: center; + padding: 10px; + border-bottom: 1px solid $separator-border-color; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + + .more { + display: block; + } + } + + .position { + font-weight: $font-semibold; + margin-right: 10px; + color: $grey-foreground-color; + } + + my-video-thumbnail { + display: flex; // Avoids an issue with line-height that adds space below the element + margin-right: 10px; + + /deep/ .video-thumbnail { + @include miniature-thumbnail(130px, 72px); + } + } + + .video-info { + display: flex; + flex-direction: column; + + a { + @include disable-default-a-behaviour; + + color: var(--mainForegroundColor); + } + + .video-info-name { + font-size: 18px; + font-weight: $font-semibold; + } + + .video-info-account, .video-info-timestamp { + color: $grey-foreground-color; + } + } + + .more { + justify-self: flex-end; + margin-left: auto; + cursor: pointer; + display: none; + + &.show { + display: block; + } + + .icon-more { + @include apply-svg-color($grey-foreground-color); + + &::after { + border: none; + } + } + + .dropdown-item { + @include dropdown-with-icon-item; + } + + .timestamp-options { + padding-top: 0; + padding-left: 35px; + margin-bottom: 15px; + + > div { + display: flex; + align-items: center; + } + + input { + @include peertube-button; + @include orange-button; + + margin-top: 10px; + } + } + } + } +} diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts index 8b70a9b1a..76aff3d4f 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts @@ -1,5 +1,5 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' -import { Notifier } from '@app/core' +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' +import { Notifier, ServerService } from '@app/core' import { AuthService } from '../../core/auth' import { ConfirmService } from '../../core/confirm' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' @@ -7,6 +7,12 @@ import { Video } from '@app/shared/video/video.model' import { Subscription } from 'rxjs' import { ActivatedRoute } from '@angular/router' import { VideoService } from '@app/shared/video/video.service' +import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' +import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { secondsToTime } from '../../../assets/player/utils' +import { VideoPlaylistElementUpdate } from '@shared/models' +import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' @Component({ selector: 'my-account-video-playlist-elements', @@ -14,7 +20,10 @@ import { VideoService } from '@app/shared/video/video.service' styleUrls: [ './my-account-video-playlist-elements.component.scss' ] }) export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestroy { + @ViewChild('moreDropdown') moreDropdown: NgbDropdown + videos: Video[] = [] + playlist: VideoPlaylist pagination: ComponentPagination = { currentPage: 1, @@ -22,21 +31,35 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro totalItems: null } + displayTimestampOptions = false + + timestampOptions: { + startTimestampEnabled: boolean + startTimestamp: number + stopTimestampEnabled: boolean + stopTimestamp: number + } = {} as any + private videoPlaylistId: string | number private paramsSub: Subscription constructor ( private authService: AuthService, + private serverService: ServerService, private notifier: Notifier, private confirmService: ConfirmService, private route: ActivatedRoute, - private videoService: VideoService + private i18n: I18n, + private videoService: VideoService, + private videoPlaylistService: VideoPlaylistService ) {} ngOnInit () { this.paramsSub = this.route.params.subscribe(routeParams => { this.videoPlaylistId = routeParams[ 'videoPlaylistId' ] this.loadElements() + + this.loadPlaylistInfo() }) } @@ -44,6 +67,46 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro if (this.paramsSub) this.paramsSub.unsubscribe() } + isVideoBlur (video: Video) { + return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) + } + + removeFromPlaylist (video: Video) { + this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, video.id) + .subscribe( + () => { + this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName })) + + this.videos = this.videos.filter(v => v.id !== video.id) + }, + + err => this.notifier.error(err.message) + ) + + this.moreDropdown.close() + } + + updateTimestamps (video: Video) { + const body: VideoPlaylistElementUpdate = {} + + body.startTimestamp = this.timestampOptions.startTimestampEnabled ? this.timestampOptions.startTimestamp : null + body.stopTimestamp = this.timestampOptions.stopTimestampEnabled ? this.timestampOptions.stopTimestamp : null + + this.videoPlaylistService.updateVideoOfPlaylist(this.playlist.id, video.id, body) + .subscribe( + () => { + this.notifier.success(this.i18n('Timestamps updated')) + + video.playlistElement.startTimestamp = body.startTimestamp + video.playlistElement.stopTimestamp = body.stopTimestamp + }, + + err => this.notifier.error(err.message) + ) + + this.moreDropdown.close() + } + onNearOfBottom () { // Last page if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return @@ -52,6 +115,50 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro this.loadElements() } + formatTimestamp (video: Video) { + const start = video.playlistElement.startTimestamp + const stop = video.playlistElement.stopTimestamp + + const startFormatted = secondsToTime(start, true, ':') + const stopFormatted = secondsToTime(stop, true, ':') + + if (start === null && stop === null) return '' + + if (start !== null && stop === null) return this.i18n('Starts at ') + startFormatted + if (start === null && stop !== null) return this.i18n('Stops at ') + stopFormatted + + return this.i18n('Starts at ') + startFormatted + this.i18n(' and stops at ') + stopFormatted + } + + onDropdownOpenChange () { + this.displayTimestampOptions = false + } + + toggleDisplayTimestampsOptions (event: Event, video: Video) { + event.preventDefault() + + this.displayTimestampOptions = !this.displayTimestampOptions + + if (this.displayTimestampOptions === true) { + this.timestampOptions = { + startTimestampEnabled: false, + stopTimestampEnabled: false, + startTimestamp: 0, + stopTimestamp: video.duration + } + + if (video.playlistElement.startTimestamp) { + this.timestampOptions.startTimestampEnabled = true + this.timestampOptions.startTimestamp = video.playlistElement.startTimestamp + } + + if (video.playlistElement.stopTimestamp) { + this.timestampOptions.stopTimestampEnabled = true + this.timestampOptions.stopTimestamp = video.playlistElement.stopTimestamp + } + } + } + private loadElements () { this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) .subscribe(({ totalVideos, videos }) => { @@ -59,4 +166,11 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro this.pagination.totalItems = totalVideos }) } + + private loadPlaylistInfo () { + this.videoPlaylistService.getVideoPlaylist(this.videoPlaylistId) + .subscribe(playlist => { + this.playlist = playlist + }) + } } diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 1e532ec13..5622b3a31 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html @@ -10,7 +10,7 @@
- +
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 137df6fc1..7b392b599 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -65,9 +65,10 @@ menu { .logged-in-more { margin-right: 20px; - .glyphicon { + my-global-icon { + @include apply-svg-color(var(--mainBackgroundColor)); + cursor: pointer; - font-size: 18px; &::after { border: none; diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html index 114b1d71f..6999474d6 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/buttons/action-dropdown.component.html @@ -3,7 +3,7 @@ class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }" ngbDropdownToggle role="button" > - + {{ label }}
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts index 3fda7ee4d..093e88033 100644 --- a/client/src/app/shared/images/global-icon.component.ts +++ b/client/src/app/shared/images/global-icon.component.ts @@ -23,7 +23,8 @@ const icons = { 'dislike': require('../../../assets/images/video/dislike.html'), 'heart': require('../../../assets/images/video/heart.html'), 'like': require('../../../assets/images/video/like.html'), - 'more': require('../../../assets/images/video/more.html'), + 'more-horizontal': require('../../../assets/images/global/more-horizontal.html'), + 'more-vertical': require('../../../assets/images/global/more-vertical.html'), 'share': require('../../../assets/images/video/share.html'), 'upload': require('../../../assets/images/video/upload.html'), 'playlist-add': require('../../../assets/images/video/playlist-add.html') diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html index ed3cd8dc5..f85e50d6d 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html @@ -2,10 +2,10 @@
Save to
-
+
- Options + Options
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss index 68dcda1eb..bc0d55912 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss @@ -18,6 +18,8 @@ } .options { + display: flex; + align-items: center; font-size: 14px; cursor: pointer; @@ -25,7 +27,8 @@ @include apply-svg-color(#333); width: 16px; - height: 16px; + height: 23px; + margin-right: 3px; } } } diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss index f8cd47f73..72158eb10 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss @@ -29,7 +29,8 @@ padding: 0 10px; display: flex; align-items: center; - font-size: 15px; + font-size: 14px; + font-weight: $font-semibold; } } diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index c936a8207..95b5e3671 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts @@ -1,5 +1,5 @@ import { User } from '../' -import { Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' +import { PlaylistElement, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model' import { durationToString, getAbsoluteAPIUrl } from '../misc/utils' @@ -47,6 +47,8 @@ export class Video implements VideoServerModel { blacklisted?: boolean blacklistedReason?: string + playlistElement?: PlaylistElement + account: { id: number uuid: string @@ -125,6 +127,8 @@ export class Video implements VideoServerModel { this.blacklistedReason = hash.blacklistedReason this.userHistory = hash.userHistory + + this.playlistElement = hash.playlistElement } isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { 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 615b88bd6..394c31f23 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -91,7 +91,7 @@
- +
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 ff321fdbc..44040e90d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -228,15 +228,7 @@ $other-videos-width: 260px; display: inline-block; .dropdown-menu .dropdown-item { - padding: 6px 24px; - - my-global-icon { - width: 24px; - - margin-right: 10px; - position: relative; - top: -2px; - } + @include dropdown-with-icon-item; } } } diff --git a/client/src/assets/images/video/more.html b/client/src/assets/images/global/more-horizontal.html similarity index 100% rename from client/src/assets/images/video/more.html rename to client/src/assets/images/global/more-horizontal.html diff --git a/client/src/assets/images/global/more-vertical.html b/client/src/assets/images/global/more-vertical.html new file mode 100644 index 000000000..9bff87a82 --- /dev/null +++ b/client/src/assets/images/global/more-vertical.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss index 36d4e84d3..25a024aac 100644 --- a/client/src/sass/include/_miniature.scss +++ b/client/src/sass/include/_miniature.scss @@ -28,15 +28,15 @@ $play-overlay-transition: 0.2s ease; $play-overlay-height: 26px; $play-overlay-width: 18px; -@mixin miniature-thumbnail { +@mixin miniature-thumbnail($width: $video-thumbnail-width, $height: $video-thumbnail-height) { @include disable-outline; display: inline-block; position: relative; border-radius: 3px; overflow: hidden; - width: $video-thumbnail-width; - height: $video-thumbnail-height; + width: $width; + height: $height; background-color: #ececec; transition: filter $play-overlay-transition; @@ -45,8 +45,8 @@ $play-overlay-width: 18px; right: 0; bottom: 0; - width: $video-thumbnail-width; - height: $video-thumbnail-height; + width: inherit; + height: inherit; opacity: 0; background-color: rgba(0, 0, 0, 0.7); @@ -87,8 +87,8 @@ $play-overlay-width: 18px; } img { - width: $video-thumbnail-width; - height: $video-thumbnail-height; + width: inherit; + height: inherit; &.blur-filter { filter: blur(5px); diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 3eefdb6fb..7faeec6bd 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -515,3 +515,15 @@ align-items: center; } } + +@mixin dropdown-with-icon-item { + padding: 6px 24px; + + my-global-icon { + width: 24px; + + margin-right: 10px; + position: relative; + top: -2px; + } +} diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 6e502b028..6de145379 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -62,11 +62,9 @@ p-table { tr { &:hover { background-color: var(--submenuColor) !important; - } - &:not(:hover) { - .action-cell * { - display: none !important; + .action-cell .dropdown-root { + display: block !important; } } @@ -140,6 +138,14 @@ p-table { padding: 0 !important; text-align: center; + .dropdown-root { + display: none !important; + + &.show { + display: block !important; + } + } + my-edit-button + my-delete-button { margin-left: 5px; } diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 6e7a6831e..963268674 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts @@ -18,6 +18,12 @@ export interface VideoFile { fps: number } +export interface PlaylistElement { + position: number + startTimestamp: number + stopTimestamp: number +} + export interface Video { id: number uuid: string @@ -55,11 +61,7 @@ export interface Video { currentTime: number } - playlistElement?: { - position: number - startTimestamp: number - stopTimestamp: number - } + playlistElement?: PlaylistElement } export interface VideoDetails extends Video {