Update playlist add component to accept multiple times the same video
This commit is contained in:
parent
cbb513e737
commit
e79df4eefb
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'
|
import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
import { secondsToTime, timeToInt } from '../../../assets/player/utils'
|
import { secondsToTime, timeToInt } from '../../../assets/player/utils'
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ export class TimestampInputComponent implements ControlValueAccessor, OnInit {
|
||||||
@Input() timestamp: number
|
@Input() timestamp: number
|
||||||
@Input() disabled = false
|
@Input() disabled = false
|
||||||
|
|
||||||
|
@Output() inputBlur = new EventEmitter()
|
||||||
|
|
||||||
timestampString: string
|
timestampString: string
|
||||||
|
|
||||||
constructor (private changeDetector: ChangeDetectorRef) {}
|
constructor (private changeDetector: ChangeDetectorRef) {}
|
||||||
|
@ -57,5 +59,7 @@ export class TimestampInputComponent implements ControlValueAccessor, OnInit {
|
||||||
|
|
||||||
this.propagateChange(this.timestamp)
|
this.propagateChange(this.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.inputBlur.emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,42 +2,6 @@
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="first-row">
|
<div class="first-row">
|
||||||
<div i18n class="title">Save to</div>
|
<div i18n class="title">Save to</div>
|
||||||
|
|
||||||
<div class="options" (click)="displayOptions = !displayOptions">
|
|
||||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
|
||||||
|
|
||||||
<span i18n>Options</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="options-row" *ngIf="displayOptions">
|
|
||||||
<div>
|
|
||||||
<my-peertube-checkbox
|
|
||||||
inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled"
|
|
||||||
i18n-labelText labelText="Start at"
|
|
||||||
></my-peertube-checkbox>
|
|
||||||
|
|
||||||
<my-timestamp-input
|
|
||||||
[timestamp]="timestampOptions.startTimestamp"
|
|
||||||
[maxTimestamp]="video.duration"
|
|
||||||
[disabled]="!timestampOptions.startTimestampEnabled"
|
|
||||||
[(ngModel)]="timestampOptions.startTimestamp"
|
|
||||||
></my-timestamp-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<my-peertube-checkbox
|
|
||||||
inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled"
|
|
||||||
i18n-labelText labelText="Stop at"
|
|
||||||
></my-peertube-checkbox>
|
|
||||||
|
|
||||||
<my-timestamp-input
|
|
||||||
[timestamp]="timestampOptions.stopTimestamp"
|
|
||||||
[maxTimestamp]="video.duration"
|
|
||||||
[disabled]="!timestampOptions.stopTimestampEnabled"
|
|
||||||
[(ngModel)]="timestampOptions.stopTimestamp"
|
|
||||||
></my-timestamp-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -46,14 +10,52 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playlists">
|
<div class="playlists">
|
||||||
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
<div
|
||||||
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist" [onPushWorkaround]="true"></my-peertube-checkbox>
|
*ngFor="let playlist of videoPlaylists"
|
||||||
|
class="playlist dropdown-item" [ngClass]="{ 'has-optional-row': playlist.optionalRowDisplayed }"
|
||||||
|
>
|
||||||
|
<div class="primary-row">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
[disabled]="isPresentMultipleTimes(playlist) || playlist.optionalRowDisplayed" [inputName]="getPrimaryInputName(playlist)"
|
||||||
|
[ngModel]="isPrimaryCheckboxChecked(playlist)" [onPushWorkaround]="true"
|
||||||
|
(click)="toggleMainPlaylist($event, playlist)"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
|
||||||
<div class="display-name">
|
<label class="display-name" (click)="toggleMainPlaylist($event, playlist)">
|
||||||
{{ playlist.displayName }}
|
{{ playlist.displayName }}
|
||||||
|
</label>
|
||||||
|
|
||||||
<div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info">
|
<div class="optional-row-icon" *ngIf="isPrimaryCheckboxChecked(playlist)" (click)="toggleOptionalRow(playlist)">
|
||||||
{{ formatTimestamp(playlist) }}
|
<my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="optional-rows" *ngIf="playlist.optionalRowDisplayed">
|
||||||
|
<div class="labels">
|
||||||
|
<div i18n>Start at</div>
|
||||||
|
<div i18n>Stop at</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngFor="let element of buildOptionalRowElements(playlist)">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
[inputName]="getOptionalInputName(playlist, element)"
|
||||||
|
[ngModel]="element.enabled" [onPushWorkaround]="true"
|
||||||
|
(click)="toggleOptionalPlaylist($event, playlist, element, startAt.timestamp, stopAt.timestamp)"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
|
||||||
|
<my-timestamp-input
|
||||||
|
[maxTimestamp]="video.duration"
|
||||||
|
[(ngModel)]="element.startTimestamp"
|
||||||
|
(inputBlur)="onElementTimestampUpdate(playlist, element)"
|
||||||
|
#startAt
|
||||||
|
></my-timestamp-input>
|
||||||
|
|
||||||
|
<my-timestamp-input
|
||||||
|
[maxTimestamp]="video.duration"
|
||||||
|
[(ngModel)]="element.stopTimestamp"
|
||||||
|
(inputBlur)="onElementTimestampUpdate(playlist, element)"
|
||||||
|
#stopAt
|
||||||
|
></my-timestamp-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
@import '_variables';
|
@import '_variables';
|
||||||
@import '_mixins';
|
@import '_mixins';
|
||||||
|
|
||||||
|
$optional-rows-checkbox-width: 34px;
|
||||||
|
$timestamp-width: 50px;
|
||||||
|
$timestamp-margin-right: 10px;
|
||||||
|
|
||||||
.header,
|
.header,
|
||||||
.dropdown-item,
|
.dropdown-item,
|
||||||
.input-container {
|
.input-container {
|
||||||
|
@ -24,31 +28,6 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
my-global-icon {
|
|
||||||
@include apply-svg-color(#333);
|
|
||||||
|
|
||||||
width: 16px;
|
|
||||||
height: 23px;
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-row {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-left: 10px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +37,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist {
|
.playlist {
|
||||||
display: inline-flex;
|
padding: 8px 10px 8px 24px;
|
||||||
cursor: pointer;
|
|
||||||
|
&.has-optional-row:hover {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-row,
|
||||||
|
.optional-rows > div {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
my-peertube-checkbox {
|
my-peertube-checkbox {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
@ -69,11 +56,58 @@
|
||||||
.display-name {
|
.display-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: $font-regular;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.timestamp-info {
|
.optional-row-icon {
|
||||||
font-size: 0.9em;
|
display: flex;
|
||||||
color: pvar(--greyForegroundColor);
|
align-items: center;
|
||||||
margin-left: 5px;
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
my-global-icon {
|
||||||
|
@include apply-svg-color(#333);
|
||||||
|
|
||||||
|
width: 19px;
|
||||||
|
height: 19px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my-timestamp-input {
|
||||||
|
margin-right: $timestamp-margin-right;
|
||||||
|
|
||||||
|
::ng-deep .ui-inputtext {
|
||||||
|
padding: 0;
|
||||||
|
width: $timestamp-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.optional-rows {
|
||||||
|
> div {
|
||||||
|
padding: 8px 5px 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-peertube-checkbox {
|
||||||
|
display: block;
|
||||||
|
width: $optional-rows-checkbox-width;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labels {
|
||||||
|
margin-left: $optional-rows-checkbox-width;
|
||||||
|
font-size: 13px;
|
||||||
|
color: pvar(--greyForegroundColor);
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-right: $timestamp-margin-right;
|
||||||
|
width: $timestamp-width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,23 +4,29 @@ import { debounceTime, filter } from 'rxjs/operators'
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
|
||||||
import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
|
import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
|
||||||
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
import { Video, VideoExistInPlaylist, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models'
|
import { Video, VideoExistInPlaylist, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy, VideoPlaylistElementUpdate } from '@shared/models'
|
||||||
import { secondsToTime } from '../../../assets/player/utils'
|
import { secondsToTime } from '../../../assets/player/utils'
|
||||||
import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
|
import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
|
||||||
import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
|
import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
|
||||||
|
import { invoke, last } from 'lodash'
|
||||||
|
|
||||||
const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
|
const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
|
||||||
|
|
||||||
type PlaylistSummary = {
|
type PlaylistElement = {
|
||||||
id: number
|
enabled: boolean
|
||||||
inPlaylist: boolean
|
|
||||||
displayName: string
|
|
||||||
|
|
||||||
playlistElementId?: number
|
playlistElementId?: number
|
||||||
startTimestamp?: number
|
startTimestamp?: number
|
||||||
stopTimestamp?: number
|
stopTimestamp?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlaylistSummary = {
|
||||||
|
id: number
|
||||||
|
displayName: string
|
||||||
|
optionalRowDisplayed: boolean
|
||||||
|
|
||||||
|
elements: PlaylistElement[]
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-add-to-playlist',
|
selector: 'my-video-add-to-playlist',
|
||||||
styleUrls: [ './video-add-to-playlist.component.scss' ],
|
styleUrls: [ './video-add-to-playlist.component.scss' ],
|
||||||
|
@ -33,16 +39,11 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
@Input() lazyLoad = false
|
@Input() lazyLoad = false
|
||||||
|
|
||||||
isNewPlaylistBlockOpened = false
|
isNewPlaylistBlockOpened = false
|
||||||
|
|
||||||
videoPlaylistSearch: string
|
videoPlaylistSearch: string
|
||||||
videoPlaylistSearchChanged = new Subject<string>()
|
videoPlaylistSearchChanged = new Subject<string>()
|
||||||
|
|
||||||
videoPlaylists: PlaylistSummary[] = []
|
videoPlaylists: PlaylistSummary[] = []
|
||||||
timestampOptions: {
|
|
||||||
startTimestampEnabled: boolean
|
|
||||||
startTimestamp: number
|
|
||||||
stopTimestampEnabled: boolean
|
|
||||||
stopTimestamp: number
|
|
||||||
}
|
|
||||||
displayOptions = false
|
|
||||||
|
|
||||||
private disabled = false
|
private disabled = false
|
||||||
|
|
||||||
|
@ -106,7 +107,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
this.videoPlaylists = []
|
this.videoPlaylists = []
|
||||||
this.videoPlaylistSearch = undefined
|
this.videoPlaylistSearch = undefined
|
||||||
|
|
||||||
this.resetOptions(true)
|
|
||||||
this.load()
|
this.load()
|
||||||
|
|
||||||
this.cd.markForCheck()
|
this.cd.markForCheck()
|
||||||
|
@ -115,7 +115,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
load () {
|
load () {
|
||||||
logger('Loading component')
|
logger('Loading component')
|
||||||
|
|
||||||
this.listenToPlaylistChanges()
|
this.listenToVideoPlaylistChange()
|
||||||
|
|
||||||
this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch)
|
this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch)
|
||||||
.subscribe(playlistsResult => {
|
.subscribe(playlistsResult => {
|
||||||
|
@ -128,7 +128,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
openChange (opened: boolean) {
|
openChange (opened: boolean) {
|
||||||
if (opened === false) {
|
if (opened === false) {
|
||||||
this.isNewPlaylistBlockOpened = false
|
this.isNewPlaylistBlockOpened = false
|
||||||
this.displayOptions = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,17 +137,49 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
this.isNewPlaylistBlockOpened = true
|
this.isNewPlaylistBlockOpened = true
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlaylist (event: Event, playlist: PlaylistSummary) {
|
toggleMainPlaylist (e: Event, playlist: PlaylistSummary) {
|
||||||
event.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (playlist.inPlaylist === true) {
|
if (this.isPresentMultipleTimes(playlist) || playlist.optionalRowDisplayed) return
|
||||||
this.removeVideoFromPlaylist(playlist)
|
|
||||||
|
if (playlist.elements.length === 0) {
|
||||||
|
const element: PlaylistElement = {
|
||||||
|
enabled: true,
|
||||||
|
playlistElementId: undefined,
|
||||||
|
startTimestamp: 0,
|
||||||
|
stopTimestamp: this.video.duration
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addVideoInPlaylist(playlist, element)
|
||||||
} else {
|
} else {
|
||||||
this.addVideoInPlaylist(playlist)
|
this.removeVideoFromPlaylist(playlist, playlist.elements[0].playlistElementId)
|
||||||
|
playlist.elements = []
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist.inPlaylist = !playlist.inPlaylist
|
this.cd.markForCheck()
|
||||||
this.resetOptions()
|
}
|
||||||
|
|
||||||
|
toggleOptionalPlaylist (e: Event, playlist: PlaylistSummary, element: PlaylistElement, startTimestamp: number, stopTimestamp: number) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (element.enabled) {
|
||||||
|
this.removeVideoFromPlaylist(playlist, element.playlistElementId)
|
||||||
|
element.enabled = false
|
||||||
|
|
||||||
|
// Hide optional rows pane when the user unchecked all the playlists
|
||||||
|
if (this.isPrimaryCheckboxChecked(playlist) === false) {
|
||||||
|
playlist.optionalRowDisplayed = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const element: PlaylistElement = {
|
||||||
|
enabled: true,
|
||||||
|
playlistElementId: undefined,
|
||||||
|
startTimestamp,
|
||||||
|
stopTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addVideoInPlaylist(playlist, element)
|
||||||
|
}
|
||||||
|
|
||||||
this.cd.markForCheck()
|
this.cd.markForCheck()
|
||||||
}
|
}
|
||||||
|
@ -172,34 +203,99 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetOptions (resetTimestamp = false) {
|
|
||||||
this.displayOptions = false
|
|
||||||
|
|
||||||
this.timestampOptions = {} as any
|
|
||||||
this.timestampOptions.startTimestampEnabled = false
|
|
||||||
this.timestampOptions.stopTimestampEnabled = false
|
|
||||||
|
|
||||||
if (resetTimestamp) {
|
|
||||||
this.timestampOptions.startTimestamp = 0
|
|
||||||
this.timestampOptions.stopTimestamp = this.video.duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatTimestamp (playlist: PlaylistSummary) {
|
|
||||||
const start = playlist.startTimestamp ? secondsToTime(playlist.startTimestamp) : ''
|
|
||||||
const stop = playlist.stopTimestamp ? secondsToTime(playlist.stopTimestamp) : ''
|
|
||||||
|
|
||||||
return `(${start}-${stop})`
|
|
||||||
}
|
|
||||||
|
|
||||||
onVideoPlaylistSearchChanged () {
|
onVideoPlaylistSearchChanged () {
|
||||||
this.videoPlaylistSearchChanged.next()
|
this.videoPlaylistSearchChanged.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeVideoFromPlaylist (playlist: PlaylistSummary) {
|
isPrimaryCheckboxChecked (playlist: PlaylistSummary) {
|
||||||
if (!playlist.playlistElementId) return
|
return playlist.elements.filter(e => e.enabled)
|
||||||
|
.length !== 0
|
||||||
|
}
|
||||||
|
|
||||||
this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, playlist.playlistElementId, this.video.id)
|
toggleOptionalRow (playlist: PlaylistSummary) {
|
||||||
|
playlist.optionalRowDisplayed = !playlist.optionalRowDisplayed
|
||||||
|
|
||||||
|
this.cd.markForCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimaryInputName (playlist: PlaylistSummary) {
|
||||||
|
return 'in-playlist-primary-' + playlist.id
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionalInputName (playlist: PlaylistSummary, element?: PlaylistElement) {
|
||||||
|
const suffix = element
|
||||||
|
? '-' + element.playlistElementId
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return 'in-playlist-optional-' + playlist.id + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
buildOptionalRowElements (playlist: PlaylistSummary) {
|
||||||
|
const elements = playlist.elements
|
||||||
|
|
||||||
|
const lastElement = elements.length === 0
|
||||||
|
? undefined
|
||||||
|
: elements[elements.length - 1]
|
||||||
|
|
||||||
|
// Build an empty last element
|
||||||
|
if (!lastElement || lastElement.enabled === true) {
|
||||||
|
elements.push({
|
||||||
|
enabled: false,
|
||||||
|
startTimestamp: 0,
|
||||||
|
stopTimestamp: this.video.duration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
|
|
||||||
|
isPresentMultipleTimes (playlist: PlaylistSummary) {
|
||||||
|
return playlist.elements.filter(e => e.enabled === true).length > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
onElementTimestampUpdate (playlist: PlaylistSummary, element: PlaylistElement) {
|
||||||
|
if (!element.playlistElementId || element.enabled === false) return
|
||||||
|
|
||||||
|
const body: VideoPlaylistElementUpdate = {
|
||||||
|
startTimestamp: element.startTimestamp,
|
||||||
|
stopTimestamp: element.stopTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoPlaylistService.updateVideoOfPlaylist(playlist.id, element.playlistElementId, body, this.video.id)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success($localize`Timestamps updated`)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => {
|
||||||
|
this.notifier.error(err.message)
|
||||||
|
},
|
||||||
|
|
||||||
|
() => this.cd.markForCheck()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private isOptionalRowDisplayed (playlist: PlaylistSummary) {
|
||||||
|
const elements = playlist.elements.filter(e => e.enabled)
|
||||||
|
|
||||||
|
if (elements.length > 1) return true
|
||||||
|
|
||||||
|
if (elements.length === 1) {
|
||||||
|
const element = elements[0]
|
||||||
|
|
||||||
|
if (
|
||||||
|
(element.startTimestamp && element.startTimestamp !== 0) ||
|
||||||
|
(element.stopTimestamp && element.stopTimestamp !== this.video.duration)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeVideoFromPlaylist (playlist: PlaylistSummary, elementId: number) {
|
||||||
|
this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, elementId, this.video.id)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.notifier.success($localize`Video removed from ${playlist.displayName}`)
|
this.notifier.success($localize`Video removed from ${playlist.displayName}`)
|
||||||
|
@ -213,7 +309,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private listenToPlaylistChanges () {
|
private listenToVideoPlaylistChange () {
|
||||||
this.unsubscribePlaylistChanges()
|
this.unsubscribePlaylistChanges()
|
||||||
|
|
||||||
this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)
|
this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)
|
||||||
|
@ -231,18 +327,30 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
|
private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
|
||||||
logger('Got existing results for %d.', this.video.id, existResult)
|
logger('Got existing results for %d.', this.video.id, existResult)
|
||||||
|
|
||||||
|
const oldPlaylists = this.videoPlaylists
|
||||||
|
|
||||||
this.videoPlaylists = []
|
this.videoPlaylists = []
|
||||||
for (const playlist of this.playlistsData) {
|
for (const playlist of this.playlistsData) {
|
||||||
const existingPlaylist = existResult.find(p => p.playlistId === playlist.id)
|
const existingPlaylists = existResult.filter(p => p.playlistId === playlist.id)
|
||||||
|
|
||||||
this.videoPlaylists.push({
|
const playlistSummary = {
|
||||||
id: playlist.id,
|
id: playlist.id,
|
||||||
|
optionalRowDisplayed: false,
|
||||||
displayName: playlist.displayName,
|
displayName: playlist.displayName,
|
||||||
inPlaylist: !!existingPlaylist,
|
elements: existingPlaylists.map(e => ({
|
||||||
playlistElementId: existingPlaylist ? existingPlaylist.playlistElementId : undefined,
|
enabled: true,
|
||||||
startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined,
|
playlistElementId: e.playlistElementId,
|
||||||
stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined
|
startTimestamp: e.startTimestamp || 0,
|
||||||
})
|
stopTimestamp: e.stopTimestamp || this.video.duration
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldPlaylist = oldPlaylists.find(p => p.id === playlist.id)
|
||||||
|
playlistSummary.optionalRowDisplayed = oldPlaylist
|
||||||
|
? oldPlaylist.optionalRowDisplayed
|
||||||
|
: this.isOptionalRowDisplayed(playlistSummary)
|
||||||
|
|
||||||
|
this.videoPlaylists.push(playlistSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
|
logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
|
||||||
|
@ -250,20 +358,22 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
this.cd.markForCheck()
|
this.cd.markForCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
private addVideoInPlaylist (playlist: PlaylistSummary) {
|
private addVideoInPlaylist (playlist: PlaylistSummary, element: PlaylistElement) {
|
||||||
const body: VideoPlaylistElementCreate = { videoId: this.video.id }
|
const body: VideoPlaylistElementCreate = { videoId: this.video.id }
|
||||||
|
|
||||||
if (this.timestampOptions.startTimestampEnabled) body.startTimestamp = this.timestampOptions.startTimestamp
|
if (element.startTimestamp) body.startTimestamp = element.startTimestamp
|
||||||
if (this.timestampOptions.stopTimestampEnabled) body.stopTimestamp = this.timestampOptions.stopTimestamp
|
if (element.stopTimestamp && element.stopTimestamp !== this.video.duration) body.stopTimestamp = element.stopTimestamp
|
||||||
|
|
||||||
this.videoPlaylistService.addVideoInPlaylist(playlist.id, body)
|
this.videoPlaylistService.addVideoInPlaylist(playlist.id, body)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
res => {
|
||||||
const message = body.startTimestamp || body.stopTimestamp
|
const message = body.startTimestamp || body.stopTimestamp
|
||||||
? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(playlist)}`
|
? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(element)}`
|
||||||
: $localize`Video added in ${playlist.displayName}`
|
: $localize`Video added in ${playlist.displayName}`
|
||||||
|
|
||||||
this.notifier.success(message)
|
this.notifier.success(message)
|
||||||
|
|
||||||
|
if (element) element.playlistElementId = res.videoPlaylistElement.id
|
||||||
},
|
},
|
||||||
|
|
||||||
err => {
|
err => {
|
||||||
|
@ -273,4 +383,11 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
|
||||||
() => this.cd.markForCheck()
|
() => this.cd.markForCheck()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private formatTimestamp (element: PlaylistElement) {
|
||||||
|
const start = element.startTimestamp ? secondsToTime(element.startTimestamp) : ''
|
||||||
|
const stop = element.stopTimestamp ? secondsToTime(element.stopTimestamp) : ''
|
||||||
|
|
||||||
|
return `(${start}-${stop})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as debug from 'debug'
|
import * as debug from 'debug'
|
||||||
import { uniq } from 'lodash-es'
|
import { uniq } from 'lodash-es'
|
||||||
import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
|
import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
|
||||||
import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators'
|
import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap, distinctUntilChanged } from 'rxjs/operators'
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http'
|
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||||
import { Injectable, NgZone } from '@angular/core'
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core'
|
import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core'
|
||||||
|
@ -53,6 +53,7 @@ export class VideoPlaylistService {
|
||||||
) {
|
) {
|
||||||
this.videoExistsInPlaylistObservable = merge(
|
this.videoExistsInPlaylistObservable = merge(
|
||||||
this.videoExistsInPlaylistNotifier.pipe(
|
this.videoExistsInPlaylistNotifier.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
// We leave Angular zone so Protractor does not get stuck
|
// We leave Angular zone so Protractor does not get stuck
|
||||||
bufferTime(500, leaveZone(this.ngZone, asyncScheduler)),
|
bufferTime(500, leaveZone(this.ngZone, asyncScheduler)),
|
||||||
filter(videoIds => videoIds.length !== 0),
|
filter(videoIds => videoIds.length !== 0),
|
||||||
|
|
Loading…
Reference in New Issue