diff options
Diffstat (limited to 'client/src/app/shared/shared-thumbnail')
5 files changed, 195 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-thumbnail/index.ts b/client/src/app/shared/shared-thumbnail/index.ts new file mode 100644 index 000000000..e09692867 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './video-thumbnail.component' | ||
2 | export * from './shared-thumbnail.module' | ||
diff --git a/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts new file mode 100644 index 000000000..8ac557c14 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedGlobalIconModule } from '../shared-icons' | ||
4 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
5 | import { VideoThumbnailComponent } from './video-thumbnail.component' | ||
6 | |||
7 | @NgModule({ | ||
8 | imports: [ | ||
9 | SharedMainModule, | ||
10 | SharedGlobalIconModule | ||
11 | ], | ||
12 | |||
13 | declarations: [ | ||
14 | VideoThumbnailComponent | ||
15 | ], | ||
16 | |||
17 | exports: [ | ||
18 | VideoThumbnailComponent | ||
19 | ], | ||
20 | |||
21 | providers: [ ] | ||
22 | }) | ||
23 | export class SharedThumbnailModule { } | ||
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html new file mode 100644 index 000000000..fe5510c56 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html | |||
@@ -0,0 +1,33 @@ | |||
1 | <a | ||
2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" | ||
3 | class="video-thumbnail" | ||
4 | > | ||
5 | <img alt="" [attr.aria-label]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> | ||
6 | |||
7 | <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay"> | ||
8 | <ng-container *ngIf="inWatchLaterPlaylist !== true"> | ||
9 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="onWatchLaterClick($event)"> | ||
10 | <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon> | ||
11 | </div> | ||
12 | </ng-container> | ||
13 | |||
14 | <ng-container *ngIf="inWatchLaterPlaylist === true"> | ||
15 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="onWatchLaterClick($event)"> | ||
16 | <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon> | ||
17 | </div> | ||
18 | </ng-container> | ||
19 | </div> | ||
20 | |||
21 | <div class="video-thumbnail-label-overlay warning"><ng-content select="label-warning"></ng-content></div> | ||
22 | <div class="video-thumbnail-label-overlay danger"><ng-content select="label-danger"></ng-content></div> | ||
23 | |||
24 | <div class="video-thumbnail-duration-overlay">{{ video.durationLabel }}</div> | ||
25 | |||
26 | <div class="play-overlay"> | ||
27 | <div class="icon"></div> | ||
28 | </div> | ||
29 | |||
30 | <div class="progress-bar" *ngIf="video.userHistory?.currentTime"> | ||
31 | <div [ngStyle]="{ 'width.%': getProgressPercent() }"></div> | ||
32 | </div> | ||
33 | </a> | ||
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss new file mode 100644 index 000000000..feff78a87 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss | |||
@@ -0,0 +1,74 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | @import '_miniature'; | ||
4 | |||
5 | .video-thumbnail { | ||
6 | @include miniature-thumbnail; | ||
7 | |||
8 | .progress-bar { | ||
9 | height: 3px; | ||
10 | width: 100%; | ||
11 | position: absolute; | ||
12 | bottom: 0; | ||
13 | background-color: rgba(0, 0, 0, 0.20); | ||
14 | |||
15 | div { | ||
16 | height: 100%; | ||
17 | background-color: pvar(--mainColor); | ||
18 | } | ||
19 | } | ||
20 | |||
21 | .video-thumbnail-watch-later-overlay, | ||
22 | .video-thumbnail-label-overlay, | ||
23 | .video-thumbnail-duration-overlay { | ||
24 | @include static-thumbnail-overlay; | ||
25 | |||
26 | border-radius: 3px; | ||
27 | font-size: 12px; | ||
28 | font-weight: $font-semibold; | ||
29 | line-height: 1.2; | ||
30 | z-index: z(miniature); | ||
31 | } | ||
32 | |||
33 | .video-thumbnail-label-overlay { | ||
34 | position: absolute; | ||
35 | padding: 0 5px; | ||
36 | left: 5px; | ||
37 | top: 5px; | ||
38 | font-weight: $font-bold; | ||
39 | |||
40 | &.warning { background-color: orange; } | ||
41 | &.danger { background-color: red; } | ||
42 | } | ||
43 | |||
44 | .video-thumbnail-duration-overlay { | ||
45 | position: absolute; | ||
46 | padding: 0 3px; | ||
47 | right: 5px; | ||
48 | bottom: 5px; | ||
49 | } | ||
50 | |||
51 | .video-thumbnail-actions-overlay { | ||
52 | position: absolute; | ||
53 | display: flex; | ||
54 | flex-direction: column; | ||
55 | right: 5px; | ||
56 | top: 5px; | ||
57 | opacity: 0; | ||
58 | |||
59 | div:not(:first-child) { | ||
60 | margin-top: 2px; | ||
61 | } | ||
62 | |||
63 | .video-thumbnail-watch-later-overlay { | ||
64 | padding: 3px; | ||
65 | |||
66 | my-global-icon { | ||
67 | width: 22px; | ||
68 | height: 22px; | ||
69 | |||
70 | @include apply-svg-color(#fff); | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | } | ||
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts new file mode 100644 index 000000000..3ff45d9b7 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||
2 | import { ScreenService } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { Video } from '../shared-main' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-video-thumbnail', | ||
8 | styleUrls: [ './video-thumbnail.component.scss' ], | ||
9 | templateUrl: './video-thumbnail.component.html' | ||
10 | }) | ||
11 | export class VideoThumbnailComponent { | ||
12 | @Input() video: Video | ||
13 | @Input() nsfw = false | ||
14 | @Input() routerLink: any[] | ||
15 | @Input() queryParams: { [ p: string ]: any } | ||
16 | |||
17 | @Input() displayWatchLaterPlaylist: boolean | ||
18 | @Input() inWatchLaterPlaylist: boolean | ||
19 | |||
20 | @Output() watchLaterClick = new EventEmitter<boolean>() | ||
21 | |||
22 | addToWatchLaterText: string | ||
23 | addedToWatchLaterText: string | ||
24 | |||
25 | constructor ( | ||
26 | private screenService: ScreenService, | ||
27 | private i18n: I18n | ||
28 | ) { | ||
29 | this.addToWatchLaterText = this.i18n('Add to watch later') | ||
30 | this.addedToWatchLaterText = this.i18n('Remove from watch later') | ||
31 | } | ||
32 | |||
33 | getImageUrl () { | ||
34 | if (!this.video) return '' | ||
35 | |||
36 | if (this.screenService.isInMobileView()) { | ||
37 | return this.video.previewUrl | ||
38 | } | ||
39 | |||
40 | return this.video.thumbnailUrl | ||
41 | } | ||
42 | |||
43 | getProgressPercent () { | ||
44 | if (!this.video.userHistory) return 0 | ||
45 | |||
46 | const currentTime = this.video.userHistory.currentTime | ||
47 | |||
48 | return (currentTime / this.video.duration) * 100 | ||
49 | } | ||
50 | |||
51 | getVideoRouterLink () { | ||
52 | if (this.routerLink) return this.routerLink | ||
53 | |||
54 | return [ '/videos/watch', this.video.uuid ] | ||
55 | } | ||
56 | |||
57 | onWatchLaterClick (event: Event) { | ||
58 | this.watchLaterClick.emit(this.inWatchLaterPlaylist) | ||
59 | |||
60 | event.stopPropagation() | ||
61 | return false | ||
62 | } | ||
63 | } | ||