diff options
author | Chocobozzz <me@florianbigard.com> | 2019-03-11 16:23:33 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-03-18 11:17:59 +0100 |
commit | c5a1ae500e68b759f76851552be6dd10631d34f4 (patch) | |
tree | f741daceab3b506f18f7cc14492a25425ebdf68d | |
parent | f0a3988066f72a28bb44520af072f18d91d77dde (diff) | |
download | PeerTube-c5a1ae500e68b759f76851552be6dd10631d34f4.tar.gz PeerTube-c5a1ae500e68b759f76851552be6dd10631d34f4.tar.zst PeerTube-c5a1ae500e68b759f76851552be6dd10631d34f4.zip |
Playlist videos component
19 files changed, 331 insertions, 45 deletions
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 @@ | |||
1 | <div class="no-results">No videos in this playlist.</div> | 1 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">No videos in this playlist.</div> |
2 | 2 | ||
3 | <div class="videos" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> | 3 | <div class="videos" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> |
4 | <div *ngFor="let video of videos" class="video"> | 4 | <div *ngFor="let video of videos" class="video"> |
5 | <my-video-thumbnail [video]="video"></my-video-thumbnail> | 5 | <div class="position">{{ video.playlistElement.position }}</div> |
6 | 6 | ||
7 | <div class="video-info"> | 7 | <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur(video)"></my-video-thumbnail> |
8 | <div class="position">{{ video.playlistElement.position }}</div> | ||
9 | |||
10 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | ||
11 | 8 | ||
9 | <div class="video-info"> | ||
12 | <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | 10 | <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> |
13 | <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a> | 11 | <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a> |
12 | <span tabindex="-1" class="video-info-timestamp">{{ formatTimestamp(video)}}</span> | ||
13 | </div> | ||
14 | |||
15 | <div class="more" ngbDropdown #moreDropdown="ngbDropdown" placement="bottom-right" (openChange)="onDropdownOpenChange()" autoClose="outside"> | ||
16 | <my-global-icon iconName="more-vertical" ngbDropdownToggle role="button" class="icon-more"></my-global-icon> | ||
17 | |||
18 | <div ngbDropdownMenu> | ||
19 | <div class="dropdown-item" (click)="toggleDisplayTimestampsOptions($event, video)"> | ||
20 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Edit starts/stops at</ng-container> | ||
21 | </div> | ||
22 | |||
23 | <div class="timestamp-options" *ngIf="displayTimestampOptions"> | ||
24 | <div> | ||
25 | <my-peertube-checkbox | ||
26 | inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled" | ||
27 | i18n-labelText labelText="Start at" | ||
28 | ></my-peertube-checkbox> | ||
29 | |||
30 | <my-timestamp-input | ||
31 | [timestamp]="timestampOptions.startTimestamp" | ||
32 | [maxTimestamp]="video.duration" | ||
33 | [disabled]="!timestampOptions.startTimestampEnabled" | ||
34 | [(ngModel)]="timestampOptions.startTimestamp" | ||
35 | ></my-timestamp-input> | ||
36 | </div> | ||
37 | |||
38 | <div> | ||
39 | <my-peertube-checkbox | ||
40 | inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled" | ||
41 | i18n-labelText labelText="Stop at" | ||
42 | ></my-peertube-checkbox> | ||
43 | |||
44 | <my-timestamp-input | ||
45 | [timestamp]="timestampOptions.stopTimestamp" | ||
46 | [maxTimestamp]="video.duration" | ||
47 | [disabled]="!timestampOptions.stopTimestampEnabled" | ||
48 | [(ngModel)]="timestampOptions.stopTimestamp" | ||
49 | ></my-timestamp-input> | ||
50 | </div> | ||
51 | |||
52 | <input type="submit" i18n-value value="Save" (click)="updateTimestamps(video)"> | ||
53 | </div> | ||
54 | |||
55 | <span class="dropdown-item" (click)="removeFromPlaylist(video)"> | ||
56 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete from {{playlist?.displayName}}</ng-container> | ||
57 | </span> | ||
58 | </div> | ||
14 | </div> | 59 | </div> |
15 | </div> | 60 | </div> |
16 | </div> | 61 | </div> |
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 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import '_miniature'; | ||
4 | |||
5 | .videos { | ||
6 | .video { | ||
7 | display: flex; | ||
8 | align-items: center; | ||
9 | padding: 10px; | ||
10 | border-bottom: 1px solid $separator-border-color; | ||
11 | |||
12 | &:hover { | ||
13 | background-color: rgba(0, 0, 0, 0.05); | ||
14 | |||
15 | .more { | ||
16 | display: block; | ||
17 | } | ||
18 | } | ||
19 | |||
20 | .position { | ||
21 | font-weight: $font-semibold; | ||
22 | margin-right: 10px; | ||
23 | color: $grey-foreground-color; | ||
24 | } | ||
25 | |||
26 | my-video-thumbnail { | ||
27 | display: flex; // Avoids an issue with line-height that adds space below the element | ||
28 | margin-right: 10px; | ||
29 | |||
30 | /deep/ .video-thumbnail { | ||
31 | @include miniature-thumbnail(130px, 72px); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | .video-info { | ||
36 | display: flex; | ||
37 | flex-direction: column; | ||
38 | |||
39 | a { | ||
40 | @include disable-default-a-behaviour; | ||
41 | |||
42 | color: var(--mainForegroundColor); | ||
43 | } | ||
44 | |||
45 | .video-info-name { | ||
46 | font-size: 18px; | ||
47 | font-weight: $font-semibold; | ||
48 | } | ||
49 | |||
50 | .video-info-account, .video-info-timestamp { | ||
51 | color: $grey-foreground-color; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | .more { | ||
56 | justify-self: flex-end; | ||
57 | margin-left: auto; | ||
58 | cursor: pointer; | ||
59 | display: none; | ||
60 | |||
61 | &.show { | ||
62 | display: block; | ||
63 | } | ||
64 | |||
65 | .icon-more { | ||
66 | @include apply-svg-color($grey-foreground-color); | ||
67 | |||
68 | &::after { | ||
69 | border: none; | ||
70 | } | ||
71 | } | ||
72 | |||
73 | .dropdown-item { | ||
74 | @include dropdown-with-icon-item; | ||
75 | } | ||
76 | |||
77 | .timestamp-options { | ||
78 | padding-top: 0; | ||
79 | padding-left: 35px; | ||
80 | margin-bottom: 15px; | ||
81 | |||
82 | > div { | ||
83 | display: flex; | ||
84 | align-items: center; | ||
85 | } | ||
86 | |||
87 | input { | ||
88 | @include peertube-button; | ||
89 | @include orange-button; | ||
90 | |||
91 | margin-top: 10px; | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | } | ||
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 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { AuthService } from '../../core/auth' | 3 | import { AuthService } from '../../core/auth' |
4 | import { ConfirmService } from '../../core/confirm' | 4 | import { ConfirmService } from '../../core/confirm' |
5 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 5 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
@@ -7,6 +7,12 @@ import { Video } from '@app/shared/video/video.model' | |||
7 | import { Subscription } from 'rxjs' | 7 | import { Subscription } from 'rxjs' |
8 | import { ActivatedRoute } from '@angular/router' | 8 | import { ActivatedRoute } from '@angular/router' |
9 | import { VideoService } from '@app/shared/video/video.service' | 9 | import { VideoService } from '@app/shared/video/video.service' |
10 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
11 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
12 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
13 | import { secondsToTime } from '../../../assets/player/utils' | ||
14 | import { VideoPlaylistElementUpdate } from '@shared/models' | ||
15 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||
10 | 16 | ||
11 | @Component({ | 17 | @Component({ |
12 | selector: 'my-account-video-playlist-elements', | 18 | selector: 'my-account-video-playlist-elements', |
@@ -14,7 +20,10 @@ import { VideoService } from '@app/shared/video/video.service' | |||
14 | styleUrls: [ './my-account-video-playlist-elements.component.scss' ] | 20 | styleUrls: [ './my-account-video-playlist-elements.component.scss' ] |
15 | }) | 21 | }) |
16 | export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestroy { | 22 | export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestroy { |
23 | @ViewChild('moreDropdown') moreDropdown: NgbDropdown | ||
24 | |||
17 | videos: Video[] = [] | 25 | videos: Video[] = [] |
26 | playlist: VideoPlaylist | ||
18 | 27 | ||
19 | pagination: ComponentPagination = { | 28 | pagination: ComponentPagination = { |
20 | currentPage: 1, | 29 | currentPage: 1, |
@@ -22,21 +31,35 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
22 | totalItems: null | 31 | totalItems: null |
23 | } | 32 | } |
24 | 33 | ||
34 | displayTimestampOptions = false | ||
35 | |||
36 | timestampOptions: { | ||
37 | startTimestampEnabled: boolean | ||
38 | startTimestamp: number | ||
39 | stopTimestampEnabled: boolean | ||
40 | stopTimestamp: number | ||
41 | } = {} as any | ||
42 | |||
25 | private videoPlaylistId: string | number | 43 | private videoPlaylistId: string | number |
26 | private paramsSub: Subscription | 44 | private paramsSub: Subscription |
27 | 45 | ||
28 | constructor ( | 46 | constructor ( |
29 | private authService: AuthService, | 47 | private authService: AuthService, |
48 | private serverService: ServerService, | ||
30 | private notifier: Notifier, | 49 | private notifier: Notifier, |
31 | private confirmService: ConfirmService, | 50 | private confirmService: ConfirmService, |
32 | private route: ActivatedRoute, | 51 | private route: ActivatedRoute, |
33 | private videoService: VideoService | 52 | private i18n: I18n, |
53 | private videoService: VideoService, | ||
54 | private videoPlaylistService: VideoPlaylistService | ||
34 | ) {} | 55 | ) {} |
35 | 56 | ||
36 | ngOnInit () { | 57 | ngOnInit () { |
37 | this.paramsSub = this.route.params.subscribe(routeParams => { | 58 | this.paramsSub = this.route.params.subscribe(routeParams => { |
38 | this.videoPlaylistId = routeParams[ 'videoPlaylistId' ] | 59 | this.videoPlaylistId = routeParams[ 'videoPlaylistId' ] |
39 | this.loadElements() | 60 | this.loadElements() |
61 | |||
62 | this.loadPlaylistInfo() | ||
40 | }) | 63 | }) |
41 | } | 64 | } |
42 | 65 | ||
@@ -44,6 +67,46 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
44 | if (this.paramsSub) this.paramsSub.unsubscribe() | 67 | if (this.paramsSub) this.paramsSub.unsubscribe() |
45 | } | 68 | } |
46 | 69 | ||
70 | isVideoBlur (video: Video) { | ||
71 | return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) | ||
72 | } | ||
73 | |||
74 | removeFromPlaylist (video: Video) { | ||
75 | this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, video.id) | ||
76 | .subscribe( | ||
77 | () => { | ||
78 | this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName })) | ||
79 | |||
80 | this.videos = this.videos.filter(v => v.id !== video.id) | ||
81 | }, | ||
82 | |||
83 | err => this.notifier.error(err.message) | ||
84 | ) | ||
85 | |||
86 | this.moreDropdown.close() | ||
87 | } | ||
88 | |||
89 | updateTimestamps (video: Video) { | ||
90 | const body: VideoPlaylistElementUpdate = {} | ||
91 | |||
92 | body.startTimestamp = this.timestampOptions.startTimestampEnabled ? this.timestampOptions.startTimestamp : null | ||
93 | body.stopTimestamp = this.timestampOptions.stopTimestampEnabled ? this.timestampOptions.stopTimestamp : null | ||
94 | |||
95 | this.videoPlaylistService.updateVideoOfPlaylist(this.playlist.id, video.id, body) | ||
96 | .subscribe( | ||
97 | () => { | ||
98 | this.notifier.success(this.i18n('Timestamps updated')) | ||
99 | |||
100 | video.playlistElement.startTimestamp = body.startTimestamp | ||
101 | video.playlistElement.stopTimestamp = body.stopTimestamp | ||
102 | }, | ||
103 | |||
104 | err => this.notifier.error(err.message) | ||
105 | ) | ||
106 | |||
107 | this.moreDropdown.close() | ||
108 | } | ||
109 | |||
47 | onNearOfBottom () { | 110 | onNearOfBottom () { |
48 | // Last page | 111 | // Last page |
49 | if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return | 112 | if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return |
@@ -52,6 +115,50 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
52 | this.loadElements() | 115 | this.loadElements() |
53 | } | 116 | } |
54 | 117 | ||
118 | formatTimestamp (video: Video) { | ||
119 | const start = video.playlistElement.startTimestamp | ||
120 | const stop = video.playlistElement.stopTimestamp | ||
121 | |||
122 | const startFormatted = secondsToTime(start, true, ':') | ||
123 | const stopFormatted = secondsToTime(stop, true, ':') | ||
124 | |||
125 | if (start === null && stop === null) return '' | ||
126 | |||
127 | if (start !== null && stop === null) return this.i18n('Starts at ') + startFormatted | ||
128 | if (start === null && stop !== null) return this.i18n('Stops at ') + stopFormatted | ||
129 | |||
130 | return this.i18n('Starts at ') + startFormatted + this.i18n(' and stops at ') + stopFormatted | ||
131 | } | ||
132 | |||
133 | onDropdownOpenChange () { | ||
134 | this.displayTimestampOptions = false | ||
135 | } | ||
136 | |||
137 | toggleDisplayTimestampsOptions (event: Event, video: Video) { | ||
138 | event.preventDefault() | ||
139 | |||
140 | this.displayTimestampOptions = !this.displayTimestampOptions | ||
141 | |||
142 | if (this.displayTimestampOptions === true) { | ||
143 | this.timestampOptions = { | ||
144 | startTimestampEnabled: false, | ||
145 | stopTimestampEnabled: false, | ||
146 | startTimestamp: 0, | ||
147 | stopTimestamp: video.duration | ||
148 | } | ||
149 | |||
150 | if (video.playlistElement.startTimestamp) { | ||
151 | this.timestampOptions.startTimestampEnabled = true | ||
152 | this.timestampOptions.startTimestamp = video.playlistElement.startTimestamp | ||
153 | } | ||
154 | |||
155 | if (video.playlistElement.stopTimestamp) { | ||
156 | this.timestampOptions.stopTimestampEnabled = true | ||
157 | this.timestampOptions.stopTimestamp = video.playlistElement.stopTimestamp | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | |||
55 | private loadElements () { | 162 | private loadElements () { |
56 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) | 163 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) |
57 | .subscribe(({ totalVideos, videos }) => { | 164 | .subscribe(({ totalVideos, videos }) => { |
@@ -59,4 +166,11 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
59 | this.pagination.totalItems = totalVideos | 166 | this.pagination.totalItems = totalVideos |
60 | }) | 167 | }) |
61 | } | 168 | } |
169 | |||
170 | private loadPlaylistInfo () { | ||
171 | this.videoPlaylistService.getVideoPlaylist(this.videoPlaylistId) | ||
172 | .subscribe(playlist => { | ||
173 | this.playlist = playlist | ||
174 | }) | ||
175 | } | ||
62 | } | 176 | } |
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 @@ | |||
10 | </div> | 10 | </div> |
11 | 11 | ||
12 | <div class="logged-in-more" ngbDropdown placement="bottom-right"> | 12 | <div class="logged-in-more" ngbDropdown placement="bottom-right"> |
13 | <span class="glyphicon glyphicon-option-vertical" ngbDropdownToggle role="button"></span> | 13 | <my-global-icon iconName="more-vertical" ngbDropdownToggle role="button"></my-global-icon> |
14 | 14 | ||
15 | <div ngbDropdownMenu> | 15 | <div ngbDropdownMenu> |
16 | <a *ngIf="user.account" i18n [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item"> | 16 | <a *ngIf="user.account" i18n [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item"> |
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 { | |||
65 | .logged-in-more { | 65 | .logged-in-more { |
66 | margin-right: 20px; | 66 | margin-right: 20px; |
67 | 67 | ||
68 | .glyphicon { | 68 | my-global-icon { |
69 | @include apply-svg-color(var(--mainBackgroundColor)); | ||
70 | |||
69 | cursor: pointer; | 71 | cursor: pointer; |
70 | font-size: 18px; | ||
71 | 72 | ||
72 | &::after { | 73 | &::after { |
73 | border: none; | 74 | 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 @@ | |||
3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }" | 3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }" |
4 | ngbDropdownToggle role="button" | 4 | ngbDropdownToggle role="button" |
5 | > | 5 | > |
6 | <my-global-icon *ngIf="!label" class="more-icon" iconName="more"></my-global-icon> | 6 | <my-global-icon *ngIf="!label" class="more-icon" iconName="more-horizontal"></my-global-icon> |
7 | <span *ngIf="label" class="dropdown-toggle">{{ label }}</span> | 7 | <span *ngIf="label" class="dropdown-toggle">{{ label }}</span> |
8 | </div> | 8 | </div> |
9 | 9 | ||
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 = { | |||
23 | 'dislike': require('../../../assets/images/video/dislike.html'), | 23 | 'dislike': require('../../../assets/images/video/dislike.html'), |
24 | 'heart': require('../../../assets/images/video/heart.html'), | 24 | 'heart': require('../../../assets/images/video/heart.html'), |
25 | 'like': require('../../../assets/images/video/like.html'), | 25 | 'like': require('../../../assets/images/video/like.html'), |
26 | 'more': require('../../../assets/images/video/more.html'), | 26 | 'more-horizontal': require('../../../assets/images/global/more-horizontal.html'), |
27 | 'more-vertical': require('../../../assets/images/global/more-vertical.html'), | ||
27 | 'share': require('../../../assets/images/video/share.html'), | 28 | 'share': require('../../../assets/images/video/share.html'), |
28 | 'upload': require('../../../assets/images/video/upload.html'), | 29 | 'upload': require('../../../assets/images/video/upload.html'), |
29 | 'playlist-add': require('../../../assets/images/video/playlist-add.html') | 30 | '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 @@ | |||
2 | <div class="first-row"> | 2 | <div class="first-row"> |
3 | <div i18n class="title">Save to</div> | 3 | <div i18n class="title">Save to</div> |
4 | 4 | ||
5 | <div i18n class="options" (click)="displayOptions = !displayOptions"> | 5 | <div class="options" (click)="displayOptions = !displayOptions"> |
6 | <my-global-icon iconName="cog"></my-global-icon> | 6 | <my-global-icon iconName="cog"></my-global-icon> |
7 | 7 | ||
8 | Options | 8 | <span i18n>Options</span> |
9 | </div> | 9 | </div> |
10 | </div> | 10 | </div> |
11 | 11 | ||
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 @@ | |||
18 | } | 18 | } |
19 | 19 | ||
20 | .options { | 20 | .options { |
21 | display: flex; | ||
22 | align-items: center; | ||
21 | font-size: 14px; | 23 | font-size: 14px; |
22 | cursor: pointer; | 24 | cursor: pointer; |
23 | 25 | ||
@@ -25,7 +27,8 @@ | |||
25 | @include apply-svg-color(#333); | 27 | @include apply-svg-color(#333); |
26 | 28 | ||
27 | width: 16px; | 29 | width: 16px; |
28 | height: 16px; | 30 | height: 23px; |
31 | margin-right: 3px; | ||
29 | } | 32 | } |
30 | } | 33 | } |
31 | } | 34 | } |
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 @@ | |||
29 | padding: 0 10px; | 29 | padding: 0 10px; |
30 | display: flex; | 30 | display: flex; |
31 | align-items: center; | 31 | align-items: center; |
32 | font-size: 15px; | 32 | font-size: 14px; |
33 | font-weight: $font-semibold; | ||
33 | } | 34 | } |
34 | } | 35 | } |
35 | 36 | ||
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 @@ | |||
1 | import { User } from '../' | 1 | import { User } from '../' |
2 | import { Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' | 2 | import { PlaylistElement, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' |
3 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 3 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
4 | import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model' | 4 | import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model' |
5 | import { durationToString, getAbsoluteAPIUrl } from '../misc/utils' | 5 | import { durationToString, getAbsoluteAPIUrl } from '../misc/utils' |
@@ -47,6 +47,8 @@ export class Video implements VideoServerModel { | |||
47 | blacklisted?: boolean | 47 | blacklisted?: boolean |
48 | blacklistedReason?: string | 48 | blacklistedReason?: string |
49 | 49 | ||
50 | playlistElement?: PlaylistElement | ||
51 | |||
50 | account: { | 52 | account: { |
51 | id: number | 53 | id: number |
52 | uuid: string | 54 | uuid: string |
@@ -125,6 +127,8 @@ export class Video implements VideoServerModel { | |||
125 | this.blacklistedReason = hash.blacklistedReason | 127 | this.blacklistedReason = hash.blacklistedReason |
126 | 128 | ||
127 | this.userHistory = hash.userHistory | 129 | this.userHistory = hash.userHistory |
130 | |||
131 | this.playlistElement = hash.playlistElement | ||
128 | } | 132 | } |
129 | 133 | ||
130 | isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { | 134 | 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 @@ | |||
91 | 91 | ||
92 | <div class="action-dropdown" ngbDropdown placement="top" role="button"> | 92 | <div class="action-dropdown" ngbDropdown placement="top" role="button"> |
93 | <div class="action-button" ngbDropdownToggle role="button"> | 93 | <div class="action-button" ngbDropdownToggle role="button"> |
94 | <my-global-icon class="more-icon" iconName="more"></my-global-icon> | 94 | <my-global-icon class="more-icon" iconName="more-horizontal"></my-global-icon> |
95 | </div> | 95 | </div> |
96 | 96 | ||
97 | <div ngbDropdownMenu> | 97 | <div ngbDropdownMenu> |
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; | |||
228 | display: inline-block; | 228 | display: inline-block; |
229 | 229 | ||
230 | .dropdown-menu .dropdown-item { | 230 | .dropdown-menu .dropdown-item { |
231 | padding: 6px 24px; | 231 | @include dropdown-with-icon-item; |
232 | |||
233 | my-global-icon { | ||
234 | width: 24px; | ||
235 | |||
236 | margin-right: 10px; | ||
237 | position: relative; | ||
238 | top: -2px; | ||
239 | } | ||
240 | } | 232 | } |
241 | } | 233 | } |
242 | } | 234 | } |
diff --git a/client/src/assets/images/video/more.html b/client/src/assets/images/global/more-horizontal.html index 39dcad10e..39dcad10e 100644 --- a/client/src/assets/images/video/more.html +++ b/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 @@ | |||
1 | <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"> | ||
2 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
3 | <g id="Artboard-4" transform="translate(-268.000000, -1046.000000)" fill="#000000"> | ||
4 | <g id="Extras" transform="translate(48.000000, 1046.000000)"> | ||
5 | <g id="more-vertical" transform="translate(220.000000, 0.000000)"> | ||
6 | <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 M10,5 C10,3.8954305 10.8877296,3 12,3 C13.1045695,3 14,3.88772964 14,5 C14,6.1045695 13.1122704,7 12,7 C10.8954305,7 10,6.11227036 10,5 Z M10,19 C10,17.8954305 10.8877296,17 12,17 C13.1045695,17 14,17.8877296 14,19 C14,20.1045695 13.1122704,21 12,21 C10.8954305,21 10,20.1122704 10,19 Z" id="Combined-Shape"/> | ||
7 | </g> | ||
8 | </g> | ||
9 | </g> | ||
10 | </g> | ||
11 | </svg> | ||
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; | |||
28 | $play-overlay-height: 26px; | 28 | $play-overlay-height: 26px; |
29 | $play-overlay-width: 18px; | 29 | $play-overlay-width: 18px; |
30 | 30 | ||
31 | @mixin miniature-thumbnail { | 31 | @mixin miniature-thumbnail($width: $video-thumbnail-width, $height: $video-thumbnail-height) { |
32 | @include disable-outline; | 32 | @include disable-outline; |
33 | 33 | ||
34 | display: inline-block; | 34 | display: inline-block; |
35 | position: relative; | 35 | position: relative; |
36 | border-radius: 3px; | 36 | border-radius: 3px; |
37 | overflow: hidden; | 37 | overflow: hidden; |
38 | width: $video-thumbnail-width; | 38 | width: $width; |
39 | height: $video-thumbnail-height; | 39 | height: $height; |
40 | background-color: #ececec; | 40 | background-color: #ececec; |
41 | transition: filter $play-overlay-transition; | 41 | transition: filter $play-overlay-transition; |
42 | 42 | ||
@@ -45,8 +45,8 @@ $play-overlay-width: 18px; | |||
45 | right: 0; | 45 | right: 0; |
46 | bottom: 0; | 46 | bottom: 0; |
47 | 47 | ||
48 | width: $video-thumbnail-width; | 48 | width: inherit; |
49 | height: $video-thumbnail-height; | 49 | height: inherit; |
50 | opacity: 0; | 50 | opacity: 0; |
51 | background-color: rgba(0, 0, 0, 0.7); | 51 | background-color: rgba(0, 0, 0, 0.7); |
52 | 52 | ||
@@ -87,8 +87,8 @@ $play-overlay-width: 18px; | |||
87 | } | 87 | } |
88 | 88 | ||
89 | img { | 89 | img { |
90 | width: $video-thumbnail-width; | 90 | width: inherit; |
91 | height: $video-thumbnail-height; | 91 | height: inherit; |
92 | 92 | ||
93 | &.blur-filter { | 93 | &.blur-filter { |
94 | filter: blur(5px); | 94 | 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 @@ | |||
515 | align-items: center; | 515 | align-items: center; |
516 | } | 516 | } |
517 | } | 517 | } |
518 | |||
519 | @mixin dropdown-with-icon-item { | ||
520 | padding: 6px 24px; | ||
521 | |||
522 | my-global-icon { | ||
523 | width: 24px; | ||
524 | |||
525 | margin-right: 10px; | ||
526 | position: relative; | ||
527 | top: -2px; | ||
528 | } | ||
529 | } | ||
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 { | |||
62 | tr { | 62 | tr { |
63 | &:hover { | 63 | &:hover { |
64 | background-color: var(--submenuColor) !important; | 64 | background-color: var(--submenuColor) !important; |
65 | } | ||
66 | 65 | ||
67 | &:not(:hover) { | 66 | .action-cell .dropdown-root { |
68 | .action-cell * { | 67 | display: block !important; |
69 | display: none !important; | ||
70 | } | 68 | } |
71 | } | 69 | } |
72 | 70 | ||
@@ -140,6 +138,14 @@ p-table { | |||
140 | padding: 0 !important; | 138 | padding: 0 !important; |
141 | text-align: center; | 139 | text-align: center; |
142 | 140 | ||
141 | .dropdown-root { | ||
142 | display: none !important; | ||
143 | |||
144 | &.show { | ||
145 | display: block !important; | ||
146 | } | ||
147 | } | ||
148 | |||
143 | my-edit-button + my-delete-button { | 149 | my-edit-button + my-delete-button { |
144 | margin-left: 5px; | 150 | margin-left: 5px; |
145 | } | 151 | } |
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 { | |||
18 | fps: number | 18 | fps: number |
19 | } | 19 | } |
20 | 20 | ||
21 | export interface PlaylistElement { | ||
22 | position: number | ||
23 | startTimestamp: number | ||
24 | stopTimestamp: number | ||
25 | } | ||
26 | |||
21 | export interface Video { | 27 | export interface Video { |
22 | id: number | 28 | id: number |
23 | uuid: string | 29 | uuid: string |
@@ -55,11 +61,7 @@ export interface Video { | |||
55 | currentTime: number | 61 | currentTime: number |
56 | } | 62 | } |
57 | 63 | ||
58 | playlistElement?: { | 64 | playlistElement?: PlaylistElement |
59 | position: number | ||
60 | startTimestamp: number | ||
61 | stopTimestamp: number | ||
62 | } | ||
63 | } | 65 | } |
64 | 66 | ||
65 | export interface VideoDetails extends Video { | 67 | export interface VideoDetails extends Video { |