From 67ed6552b831df66713bac9e672738796128d33f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Jun 2020 14:10:17 +0200 Subject: Reorganize client shared modules --- .../video-add-to-playlist.component.html | 82 ----- .../video-add-to-playlist.component.scss | 107 ------ .../video-add-to-playlist.component.ts | 280 ---------------- ...video-playlist-element-miniature.component.html | 92 ------ ...video-playlist-element-miniature.component.scss | 224 ------------- .../video-playlist-element-miniature.component.ts | 187 ----------- .../video-playlist/video-playlist-element.model.ts | 24 -- .../video-playlist-miniature.component.html | 34 -- .../video-playlist-miniature.component.scss | 78 ----- .../video-playlist-miniature.component.ts | 22 -- .../shared/video-playlist/video-playlist.model.ts | 97 ------ .../video-playlist/video-playlist.service.ts | 357 --------------------- 12 files changed, 1584 deletions(-) delete mode 100644 client/src/app/shared/video-playlist/video-add-to-playlist.component.html delete mode 100644 client/src/app/shared/video-playlist/video-add-to-playlist.component.scss delete mode 100644 client/src/app/shared/video-playlist/video-add-to-playlist.component.ts delete mode 100644 client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html delete mode 100644 client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss delete mode 100644 client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts delete mode 100644 client/src/app/shared/video-playlist/video-playlist-element.model.ts delete mode 100644 client/src/app/shared/video-playlist/video-playlist-miniature.component.html delete mode 100644 client/src/app/shared/video-playlist/video-playlist-miniature.component.scss delete mode 100644 client/src/app/shared/video-playlist/video-playlist-miniature.component.ts delete mode 100644 client/src/app/shared/video-playlist/video-playlist.model.ts delete mode 100644 client/src/app/shared/video-playlist/video-playlist.service.ts (limited to 'client/src/app/shared/video-playlist') 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 deleted file mode 100644 index a40e0699e..000000000 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html +++ /dev/null @@ -1,82 +0,0 @@ -
-
-
-
Save to
- -
- - - 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 deleted file mode 100644 index 47baa997b..000000000 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss +++ /dev/null @@ -1,107 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.header, -.dropdown-item, -.input-container { - padding: 8px 24px; -} - -.header { - min-width: 240px; - margin-bottom: 10px; - border-bottom: 1px solid $separator-border-color; - - .first-row { - display: flex; - align-items: center; - - .title { - font-size: 18px; - 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; - } - } -} - -.playlists { - max-height: 180px; - overflow-y: auto; -} - -.playlist { - display: inline-flex; - cursor: pointer; - - my-peertube-checkbox { - margin-right: 10px; - align-self: center; - } - - .display-name { - display: flex; - align-items: flex-end; - - .timestamp-info { - font-size: 0.9em; - color: pvar(--greyForegroundColor); - margin-left: 5px; - } - } -} - -.new-playlist-button, -.new-playlist-block { - padding-top: 10px; - border-top: 1px solid $separator-border-color; -} - -.new-playlist-button { - cursor: pointer; - - my-global-icon { - @include apply-svg-color(#333); - - position: relative; - left: -1px; - top: -1px; - margin-right: 4px; - width: 21px; - height: 21px; - } -} - -input[type=text] { - @include peertube-input-text(200px); - - display: block; -} - -input[type=submit] { - @include peertube-button; - @include orange-button; -} diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts deleted file mode 100644 index 0c593a79a..000000000 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' -import { CachedPlaylist, VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' -import { AuthService, Notifier } from '@app/core' -import { Subject, Subscription } from 'rxjs' -import { debounceTime, filter } from 'rxjs/operators' -import { Video, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' -import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/forms' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { secondsToTime } from '../../../assets/player/utils' -import * as debug from 'debug' -import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' -import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' - -const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') - -type PlaylistSummary = { - id: number - inPlaylist: boolean - displayName: string - - playlistElementId?: number - startTimestamp?: number - stopTimestamp?: number -} - -@Component({ - selector: 'my-video-add-to-playlist', - styleUrls: [ './video-add-to-playlist.component.scss' ], - templateUrl: './video-add-to-playlist.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, OnChanges, OnDestroy, DisableForReuseHook { - @Input() video: Video - @Input() currentVideoTimestamp: number - @Input() lazyLoad = false - - isNewPlaylistBlockOpened = false - videoPlaylistSearch: string - videoPlaylistSearchChanged = new Subject() - videoPlaylists: PlaylistSummary[] = [] - timestampOptions: { - startTimestampEnabled: boolean - startTimestamp: number - stopTimestampEnabled: boolean - stopTimestamp: number - } - displayOptions = false - - private disabled = false - - private listenToPlaylistChangeSub: Subscription - private playlistsData: CachedPlaylist[] = [] - - constructor ( - protected formValidatorService: FormValidatorService, - private authService: AuthService, - private notifier: Notifier, - private i18n: I18n, - private videoPlaylistService: VideoPlaylistService, - private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, - private cd: ChangeDetectorRef - ) { - super() - } - - get user () { - return this.authService.getUser() - } - - ngOnInit () { - this.buildForm({ - displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME - }) - - this.videoPlaylistService.listenToMyAccountPlaylistsChange() - .subscribe(result => { - this.playlistsData = result.data - - this.videoPlaylistService.runPlaylistCheck(this.video.id) - }) - - this.videoPlaylistSearchChanged - .pipe(debounceTime(500)) - .subscribe(() => this.load()) - - if (this.lazyLoad === false) this.load() - } - - ngOnChanges (simpleChanges: SimpleChanges) { - if (simpleChanges['video']) { - this.reload() - } - } - - ngOnDestroy () { - this.unsubscribePlaylistChanges() - } - - disableForReuse () { - this.disabled = true - } - - enabledForReuse () { - this.disabled = false - } - - reload () { - logger('Reloading component') - - this.videoPlaylists = [] - this.videoPlaylistSearch = undefined - - this.resetOptions(true) - this.load() - - this.cd.markForCheck() - } - - load () { - logger('Loading component') - - this.listenToPlaylistChanges() - - this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch) - .subscribe(playlistsResult => { - this.playlistsData = playlistsResult.data - - this.videoPlaylistService.runPlaylistCheck(this.video.id) - }) - } - - openChange (opened: boolean) { - if (opened === false) { - this.isNewPlaylistBlockOpened = false - this.displayOptions = false - } - } - - openCreateBlock (event: Event) { - event.preventDefault() - - this.isNewPlaylistBlockOpened = true - } - - togglePlaylist (event: Event, playlist: PlaylistSummary) { - event.preventDefault() - - if (playlist.inPlaylist === true) { - this.removeVideoFromPlaylist(playlist) - } else { - this.addVideoInPlaylist(playlist) - } - - playlist.inPlaylist = !playlist.inPlaylist - this.resetOptions() - - this.cd.markForCheck() - } - - createPlaylist () { - const displayName = this.form.value[ 'displayName' ] - - const videoPlaylistCreate: VideoPlaylistCreate = { - displayName, - privacy: VideoPlaylistPrivacy.PRIVATE - } - - this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe( - () => { - this.isNewPlaylistBlockOpened = false - - this.cd.markForCheck() - }, - - err => this.notifier.error(err.message) - ) - } - - 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 () { - this.videoPlaylistSearchChanged.next() - } - - private removeVideoFromPlaylist (playlist: PlaylistSummary) { - if (!playlist.playlistElementId) return - - this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, playlist.playlistElementId, this.video.id) - .subscribe( - () => { - this.notifier.success(this.i18n('Video removed from {{name}}', { name: playlist.displayName })) - }, - - err => { - this.notifier.error(err.message) - }, - - () => this.cd.markForCheck() - ) - } - - private listenToPlaylistChanges () { - this.unsubscribePlaylistChanges() - - this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id) - .pipe(filter(() => this.disabled === false)) - .subscribe(existResult => this.rebuildPlaylists(existResult)) - } - - private unsubscribePlaylistChanges () { - if (this.listenToPlaylistChangeSub) { - this.listenToPlaylistChangeSub.unsubscribe() - this.listenToPlaylistChangeSub = undefined - } - } - - private rebuildPlaylists (existResult: VideoExistInPlaylist[]) { - logger('Got existing results for %d.', this.video.id, existResult) - - this.videoPlaylists = [] - for (const playlist of this.playlistsData) { - const existingPlaylist = existResult.find(p => p.playlistId === playlist.id) - - this.videoPlaylists.push({ - id: playlist.id, - displayName: playlist.displayName, - inPlaylist: !!existingPlaylist, - playlistElementId: existingPlaylist ? existingPlaylist.playlistElementId : undefined, - startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined, - stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined - }) - } - - logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists) - - this.cd.markForCheck() - } - - private addVideoInPlaylist (playlist: PlaylistSummary) { - const body: VideoPlaylistElementCreate = { videoId: this.video.id } - - if (this.timestampOptions.startTimestampEnabled) body.startTimestamp = this.timestampOptions.startTimestamp - if (this.timestampOptions.stopTimestampEnabled) body.stopTimestamp = this.timestampOptions.stopTimestamp - - this.videoPlaylistService.addVideoInPlaylist(playlist.id, body) - .subscribe( - () => { - const message = body.startTimestamp || body.stopTimestamp - ? this.i18n('Video added in {{n}} at timestamps {{t}}', { n: playlist.displayName, t: this.formatTimestamp(playlist) }) - : this.i18n('Video added in {{n}}', { n: playlist.displayName }) - - this.notifier.success(message) - }, - - err => { - this.notifier.error(err.message) - }, - - () => this.cd.markForCheck() - ) - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html deleted file mode 100644 index e3f7ef017..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html +++ /dev/null @@ -1,92 +0,0 @@ -
- -
- - {{ position }} -
- - - -
- -
- - {{ playlistElement.video.name }} - - - - - {{ formatTimestamp(playlistElement) }} - - - - Unavailable - Private - Deleted - -
- - - - -
- - -
- - - -
-
- - - -
- -
- - - -
- - -
-
- - - - Delete from {{ playlist?.displayName }} - -
-
-
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss deleted file mode 100644 index afd775b25..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss +++ /dev/null @@ -1,224 +0,0 @@ -@import '_variables'; -@import '_mixins'; -@import '_miniature'; - -$thumbnail-width: 130px; -$thumbnail-height: 72px; - -my-video-thumbnail { - @include thumbnail-size-component($thumbnail-width, $thumbnail-height); -} - -.fake-thumbnail { - width: $thumbnail-width; - height: $thumbnail-height; - background-color: #ececec; -} - -my-video-thumbnail, -.fake-thumbnail { - display: flex; // Avoids an issue with line-height that adds space below the element - margin-right: 10px; -} - -.video { - display: flex; - align-items: center; - background-color: pvar(--mainBackgroundColor); - padding: 10px; - border-bottom: 1px solid $separator-border-color; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - - .more { - opacity: 1; - } - } - - @media not all and (hover: hover) and (pointer: fine) { - .more { - opacity: 1 !important; - } - } - - &.playing { - background-color: rgba(0, 0, 0, 0.02); - } - - a { - @include disable-default-a-behaviour; - - color: pvar(--mainForegroundColor); - display: flex; - min-width: 0; - align-items: center; - cursor: pointer; - - .position { - font-weight: $font-semibold; - margin-right: 10px; - color: pvar(--greyForegroundColor); - min-width: 25px; - - my-global-icon { - @include apply-svg-color(pvar(--greyForegroundColor)); - - width: 17px; - position: relative; - left: -2px; - } - } - - .video-info { - display: flex; - flex-direction: column; - align-self: flex-start; - min-width: 0; - - a { - width: auto; - } - - .video-info-account, .video-info-timestamp { - color: pvar(--greyForegroundColor); - } - } - } - - .video-info-name { - font-size: 18px; - font-weight: $font-semibold; - display: inline-block; - - @include ellipsis; - } - - .more, my-edit-button { - justify-self: flex-end; - margin-left: auto; - cursor: pointer; - min-width: 24px; - } - - .more { - opacity: 0; - - &.show { - opacity: 1; - } - - .icon-more { - @include apply-svg-color(pvar(--greyForegroundColor)); - - display: flex; - - &::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; - } - } - } -} - -@mixin more-dropdown-control { - .video { - my-edit-button { - display: none; - - + .more { - display: inline-flex; - } - } - } -} - -@mixin edit-button-control { - .video { - my-edit-button { - display: none; - } - - &.playing { - my-edit-button { - display: inline-flex; - height: max-content; - } - } - - my-edit-button + .more { - display: none; - } - } -} - -@mixin edit-button-in-mobile-view { - .video { - my-edit-button { - ::ng-deep .action-button-edit { - padding: 0 13px; - - .button-label { - display: none; - } - } - } - } -} - -@media screen and (min-width: $small-view) { - :host-context(.expanded) { - @include more-dropdown-control(); - } -} - -@media screen and (max-width: $small-view) { - :host-context(.expanded) { - @include edit-button-control(); - } -} - -@media screen and (max-width: $mobile-view) { - :host-context(.expanded) { - @include edit-button-in-mobile-view(); - } -} - -@media screen and (min-width: #{$small-view + $menu-width}) { - :host-context(.main-col:not(.expanded)) { - @include more-dropdown-control(); - } -} - -@media screen and (max-width: #{$small-view + $menu-width}) { - :host-context(.main-col:not(.expanded)) { - @include edit-button-control(); - } -} - -@media screen and (max-width: #{$mobile-view + $menu-width}) { - :host-context(.main-col:not(.expanded)) { - @include edit-button-in-mobile-view(); - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts deleted file mode 100644 index fad03e045..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' -import { Video } from '@app/shared/video/video.model' -import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' -import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' -import { ActivatedRoute } from '@angular/router' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoService } from '@app/shared/video/video.service' -import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' -import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' -import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' -import { secondsToTime } from '../../../assets/player/utils' -import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' - -@Component({ - selector: 'my-video-playlist-element-miniature', - styleUrls: [ './video-playlist-element-miniature.component.scss' ], - templateUrl: './video-playlist-element-miniature.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class VideoPlaylistElementMiniatureComponent implements OnInit { - @ViewChild('moreDropdown') moreDropdown: NgbDropdown - - @Input() playlist: VideoPlaylist - @Input() playlistElement: VideoPlaylistElement - @Input() owned = false - @Input() playing = false - @Input() rowLink = false - @Input() accountLink = true - @Input() position: number // Keep this property because we're in the OnPush change detection strategy - @Input() touchScreenEditButton = false - - @Output() elementRemoved = new EventEmitter() - - displayTimestampOptions = false - - timestampOptions: { - startTimestampEnabled: boolean - startTimestamp: number - stopTimestampEnabled: boolean - stopTimestamp: number - } = {} as any - - private serverConfig: ServerConfig - - constructor ( - private authService: AuthService, - private serverService: ServerService, - private notifier: Notifier, - private confirmService: ConfirmService, - private route: ActivatedRoute, - private i18n: I18n, - private videoService: VideoService, - private videoPlaylistService: VideoPlaylistService, - private cdr: ChangeDetectorRef - ) {} - - ngOnInit (): void { - this.serverConfig = this.serverService.getTmpConfig() - this.serverService.getConfig() - .subscribe(config => { - this.serverConfig = config - this.cdr.detectChanges() - }) - } - - isUnavailable (e: VideoPlaylistElement) { - return e.type === VideoPlaylistElementType.UNAVAILABLE - } - - isPrivate (e: VideoPlaylistElement) { - return e.type === VideoPlaylistElementType.PRIVATE - } - - isDeleted (e: VideoPlaylistElement) { - return e.type === VideoPlaylistElementType.DELETED - } - - buildRouterLink () { - if (!this.playlist) return null - - return [ '/videos/watch/playlist', this.playlist.uuid ] - } - - buildRouterQuery () { - if (!this.playlistElement || !this.playlistElement.video) return {} - - return { - videoId: this.playlistElement.video.uuid, - start: this.playlistElement.startTimestamp, - stop: this.playlistElement.stopTimestamp, - resume: true - } - } - - isVideoBlur (video: Video) { - return video.isVideoNSFWForUser(this.authService.getUser(), this.serverConfig) - } - - removeFromPlaylist (playlistElement: VideoPlaylistElement) { - const videoId = this.playlistElement.video ? this.playlistElement.video.id : undefined - - this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, playlistElement.id, videoId) - .subscribe( - () => { - this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName })) - - this.elementRemoved.emit(playlistElement) - }, - - err => this.notifier.error(err.message) - ) - - this.moreDropdown.close() - } - - updateTimestamps (playlistElement: VideoPlaylistElement) { - 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, playlistElement.id, body, this.playlistElement.video.id) - .subscribe( - () => { - this.notifier.success(this.i18n('Timestamps updated')) - - playlistElement.startTimestamp = body.startTimestamp - playlistElement.stopTimestamp = body.stopTimestamp - - this.cdr.detectChanges() - }, - - err => this.notifier.error(err.message) - ) - - this.moreDropdown.close() - } - - formatTimestamp (playlistElement: VideoPlaylistElement) { - const start = playlistElement.startTimestamp - const stop = 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, playlistElement: VideoPlaylistElement) { - event.preventDefault() - - this.displayTimestampOptions = !this.displayTimestampOptions - - if (this.displayTimestampOptions === true) { - this.timestampOptions = { - startTimestampEnabled: false, - stopTimestampEnabled: false, - startTimestamp: 0, - stopTimestamp: playlistElement.video.duration - } - - if (playlistElement.startTimestamp) { - this.timestampOptions.startTimestampEnabled = true - this.timestampOptions.startTimestamp = playlistElement.startTimestamp - } - - if (playlistElement.stopTimestamp) { - this.timestampOptions.stopTimestampEnabled = true - this.timestampOptions.stopTimestamp = playlistElement.stopTimestamp - } - } - - // FIXME: why do we have to use setTimeout here? - setTimeout(() => { - this.cdr.detectChanges() - }) - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist-element.model.ts b/client/src/app/shared/video-playlist/video-playlist-element.model.ts deleted file mode 100644 index f1c46d1eb..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-element.model.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { VideoPlaylistElement as ServerVideoPlaylistElement, VideoPlaylistElementType } from '../../../../../shared/models/videos' -import { Video } from '@app/shared/video/video.model' - -export class VideoPlaylistElement implements ServerVideoPlaylistElement { - id: number - position: number - startTimestamp: number - stopTimestamp: number - - type: VideoPlaylistElementType - - video?: Video - - constructor (hash: ServerVideoPlaylistElement, translations: {}) { - this.id = hash.id - this.position = hash.position - this.startTimestamp = hash.startTimestamp - this.stopTimestamp = hash.stopTimestamp - - this.type = hash.type - - if (hash.video) this.video = new Video(hash.video, translations) - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html deleted file mode 100644 index 86f6664cb..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
- - - -
- {playlist.videosLength, plural, =0 {No videos} =1 {1 video} other {{{ playlist.videosLength }} videos}} -
- -
-
-
-
- -
- - {{ playlist.displayName }} - - - - {{ playlist.videoChannelBy }} - - -
- {{ playlist.privacy.label }} - - Updated {{ playlist.updatedAt | myFromNow }} -
- -
{{ playlist.description }}
-
-
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 deleted file mode 100644 index 1b16dbb01..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss +++ /dev/null @@ -1,78 +0,0 @@ -@import '_variables'; -@import '_mixins'; -@import '_miniature'; - -.miniature { - display: inline-block; - - &.no-videos:not(.to-manage){ - a { - cursor: default !important; - } - } - - &.to-manage, - &.no-videos { - .play-overlay { - display: none; - } - } - - .miniature-thumbnail { - @include miniature-thumbnail; - - .miniature-playlist-info-overlay { - @include static-thumbnail-overlay; - - position: absolute; - right: 0; - bottom: 0; - height: $video-thumbnail-height; - padding: 0 10px; - display: flex; - align-items: center; - font-size: 14px; - font-weight: $font-semibold; - } - } - - .miniature-info { - width: 200px; - margin-top: 2px; - line-height: normal; - - .miniature-name { - @include miniature-name; - - @include ellipsis-multiline(1.3em, 2); - - margin: 0; - } - - .by { - @include disable-default-a-behaviour; - - display: block; - color: pvar(--greyForegroundColor); - } - - .privacy-date { - margin-top: 5px; - - .video-info-privacy { - font-size: 14px; - font-weight: $font-semibold; - - &::after { - content: '-'; - margin: 0 3px; - } - } - } - - .video-info-description { - margin-top: 10px; - color: pvar(--greyForegroundColor); - } - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts deleted file mode 100644 index 523e96f2a..000000000 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, Input } from '@angular/core' -import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' - -@Component({ - selector: 'my-video-playlist-miniature', - styleUrls: [ './video-playlist-miniature.component.scss' ], - templateUrl: './video-playlist-miniature.component.html' -}) -export class VideoPlaylistMiniatureComponent { - @Input() playlist: VideoPlaylist - @Input() toManage = false - @Input() displayChannel = false - @Input() displayDescription = false - @Input() displayPrivacy = false - - getPlaylistUrl () { - if (this.toManage) return [ '/my-account/video-playlists', this.playlist.uuid ] - if (this.playlist.videosLength === 0) return null - - return [ '/videos/watch/playlist', this.playlist.uuid ] - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist.model.ts b/client/src/app/shared/video-playlist/video-playlist.model.ts deleted file mode 100644 index 6f27e7475..000000000 --- a/client/src/app/shared/video-playlist/video-playlist.model.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - VideoChannelSummary, - VideoConstant, - VideoPlaylist as ServerVideoPlaylist, - VideoPlaylistPrivacy, - VideoPlaylistType -} from '../../../../../shared/models/videos' -import { AccountSummary, peertubeTranslate } from '@shared/models' -import { Actor } from '@app/shared/actor/actor.model' -import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' - -export class VideoPlaylist implements ServerVideoPlaylist { - id: number - uuid: string - isLocal: boolean - - displayName: string - description: string - privacy: VideoConstant - - thumbnailPath: string - - videosLength: number - - type: VideoConstant - - createdAt: Date | string - updatedAt: Date | string - - ownerAccount: AccountSummary - videoChannel?: VideoChannelSummary - - thumbnailUrl: string - - ownerBy: string - ownerAvatarUrl: string - - videoChannelBy?: string - videoChannelAvatarUrl?: string - - private thumbnailVersion: number - private originThumbnailUrl: string - - constructor (hash: ServerVideoPlaylist, translations: {}) { - const absoluteAPIUrl = getAbsoluteAPIUrl() - - this.id = hash.id - this.uuid = hash.uuid - this.isLocal = hash.isLocal - - this.displayName = hash.displayName - - this.description = hash.description - this.privacy = hash.privacy - - this.thumbnailPath = hash.thumbnailPath - - if (this.thumbnailPath) { - this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath - this.originThumbnailUrl = this.thumbnailUrl - } else { - this.thumbnailUrl = window.location.origin + '/client/assets/images/default-playlist.jpg' - } - - this.videosLength = hash.videosLength - - this.type = hash.type - - this.createdAt = new Date(hash.createdAt) - this.updatedAt = new Date(hash.updatedAt) - - this.ownerAccount = hash.ownerAccount - this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) - this.ownerAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.ownerAccount) - - if (hash.videoChannel) { - this.videoChannel = hash.videoChannel - this.videoChannelBy = Actor.CREATE_BY_STRING(hash.videoChannel.name, hash.videoChannel.host) - this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.videoChannel) - } - - this.privacy.label = peertubeTranslate(this.privacy.label, translations) - - if (this.type.id === VideoPlaylistType.WATCH_LATER) { - this.displayName = peertubeTranslate(this.displayName, translations) - } - } - - refreshThumbnail () { - if (!this.originThumbnailUrl) return - - if (!this.thumbnailVersion) this.thumbnailVersion = 0 - this.thumbnailVersion++ - - this.thumbnailUrl = this.originThumbnailUrl + '?v' + this.thumbnailVersion - } -} diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts deleted file mode 100644 index 38d915c6b..000000000 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' -import { Injectable, NgZone } from '@angular/core' -import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' -import { RestExtractor } from '../rest/rest-extractor.service' -import { HttpClient, HttpParams } from '@angular/common/http' -import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared' -import { environment } from '../../../environments/environment' -import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' -import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' -import { VideoChannel } from '@app/shared/video-channel/video-channel.model' -import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' -import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' -import { objectToFormData } from '@app/shared/misc/utils' -import { AuthUser, ServerService } from '@app/core' -import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' -import { AccountService } from '@app/shared/account/account.service' -import { Account } from '@app/shared/account/account.model' -import { RestService } from '@app/shared/rest' -import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model' -import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model' -import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' -import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model' -import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' -import { uniq } from 'lodash-es' -import * as debug from 'debug' -import { enterZone, leaveZone } from '@app/shared/rxjs/zone' - -const logger = debug('peertube:playlists:VideoPlaylistService') - -export type CachedPlaylist = VideoPlaylist | { id: number, displayName: string } - -@Injectable() -export class VideoPlaylistService { - static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' - static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/' - - // Use a replay subject because we "next" a value before subscribing - private videoExistsInPlaylistNotifier = new ReplaySubject(1) - private videoExistsInPlaylistCacheSubject = new Subject() - private readonly videoExistsInPlaylistObservable: Observable - - private videoExistsObservableCache: { [ id: number ]: Observable } = {} - private videoExistsCache: { [ id: number ]: VideoExistInPlaylist[] } = {} - - private myAccountPlaylistCache: ResultList = undefined - private myAccountPlaylistCacheRunning: Observable> - private myAccountPlaylistCacheSubject = new Subject>() - - constructor ( - private authHttp: HttpClient, - private serverService: ServerService, - private restExtractor: RestExtractor, - private restService: RestService, - private ngZone: NgZone - ) { - this.videoExistsInPlaylistObservable = merge( - this.videoExistsInPlaylistNotifier.pipe( - // We leave Angular zone so Protractor does not get stuck - bufferTime(500, leaveZone(this.ngZone, asyncScheduler)), - filter(videoIds => videoIds.length !== 0), - map(videoIds => uniq(videoIds)), - observeOn(enterZone(this.ngZone, asyncScheduler)), - switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), - share() - ), - - this.videoExistsInPlaylistCacheSubject - ) - } - - listChannelPlaylists (videoChannel: VideoChannel, componentPagination: ComponentPaginationLight): Observable> { - const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists' - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) - - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination) - - return this.authHttp.get>(url, { params }) - .pipe( - switchMap(res => this.extractPlaylists(res)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - listMyPlaylistWithCache (user: AuthUser, search?: string) { - if (!search) { - if (this.myAccountPlaylistCacheRunning) return this.myAccountPlaylistCacheRunning - if (this.myAccountPlaylistCache) return of(this.myAccountPlaylistCache) - } - - const obs = this.listAccountPlaylists(user.account, undefined, '-updatedAt', search) - .pipe( - tap(result => { - if (!search) { - this.myAccountPlaylistCacheRunning = undefined - this.myAccountPlaylistCache = result - } - }), - share() - ) - - if (!search) this.myAccountPlaylistCacheRunning = obs - return obs - } - - listAccountPlaylists ( - account: Account, - componentPagination: ComponentPaginationLight, - sort: string, - search?: string - ): Observable> { - const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists' - const pagination = componentPagination - ? this.restService.componentPaginationToRestPagination(componentPagination) - : undefined - - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination, sort) - if (search) params = this.restService.addObjectParams(params, { search }) - - return this.authHttp.get>(url, { params }) - .pipe( - switchMap(res => this.extractPlaylists(res)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getVideoPlaylist (id: string | number) { - const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + id - - return this.authHttp.get(url) - .pipe( - switchMap(res => this.extractPlaylist(res)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - createVideoPlaylist (body: VideoPlaylistCreate) { - const data = objectToFormData(body) - - return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) - .pipe( - tap(res => { - if (!this.myAccountPlaylistCache) return - - this.myAccountPlaylistCache.total++ - - this.myAccountPlaylistCache.data.push({ - id: res.videoPlaylist.id, - displayName: body.displayName - }) - - this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateVideoPlaylist (videoPlaylist: VideoPlaylist, body: VideoPlaylistUpdate) { - const data = objectToFormData(body) - - return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => { - if (!this.myAccountPlaylistCache) return - - const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id) - playlist.displayName = body.displayName - - this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - removeVideoPlaylist (videoPlaylist: VideoPlaylist) { - return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => { - if (!this.myAccountPlaylistCache) return - - this.myAccountPlaylistCache.total-- - this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data - .filter(p => p.id !== videoPlaylist.id) - - this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - addVideoInPlaylist (playlistId: number, body: VideoPlaylistElementCreate) { - const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos' - - return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body) - .pipe( - tap(res => { - const existsResult = this.videoExistsCache[body.videoId] - existsResult.push({ - playlistId, - playlistElementId: res.videoPlaylistElement.id, - startTimestamp: body.startTimestamp, - stopTimestamp: body.stopTimestamp - }) - - this.runPlaylistCheck(body.videoId) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate, videoId: number) { - return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId, body) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => { - const existsResult = this.videoExistsCache[videoId] - const elem = existsResult.find(e => e.playlistElementId === playlistElementId) - - elem.startTimestamp = body.startTimestamp - elem.stopTimestamp = body.stopTimestamp - - this.runPlaylistCheck(videoId) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - removeVideoFromPlaylist (playlistId: number, playlistElementId: number, videoId?: number) { - return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => { - if (!videoId) return - - this.videoExistsCache[videoId] = this.videoExistsCache[videoId].filter(e => e.playlistElementId !== playlistElementId) - this.runPlaylistCheck(videoId) - }), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - reorderPlaylist (playlistId: number, oldPosition: number, newPosition: number) { - const body: VideoPlaylistReorder = { - startPosition: oldPosition, - insertAfterPosition: newPosition - } - - return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/reorder', body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getPlaylistVideos ( - videoPlaylistId: number | string, - componentPagination: ComponentPaginationLight - ): Observable> { - const path = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylistId + '/videos' - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) - - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination) - - return this.authHttp - .get>(path, { params }) - .pipe( - switchMap(res => this.extractVideoPlaylistElements(res)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - listenToMyAccountPlaylistsChange () { - return this.myAccountPlaylistCacheSubject.asObservable() - } - - listenToVideoPlaylistChange (videoId: number) { - if (this.videoExistsObservableCache[ videoId ]) { - return this.videoExistsObservableCache[ videoId ] - } - - const obs = this.videoExistsInPlaylistObservable - .pipe( - map(existsResult => existsResult[ videoId ]), - filter(r => !!r), - tap(result => this.videoExistsCache[ videoId ] = result) - ) - - this.videoExistsObservableCache[ videoId ] = obs - return obs - } - - runPlaylistCheck (videoId: number) { - logger('Running playlist check.') - - if (this.videoExistsCache[videoId]) { - logger('Found cache for %d.', videoId) - - return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] }) - } - - logger('Fetching from network for %d.', videoId) - return this.videoExistsInPlaylistNotifier.next(videoId) - } - - extractPlaylists (result: ResultList) { - return this.serverService.getServerLocale() - .pipe( - map(translations => { - const playlistsJSON = result.data - const total = result.total - const playlists: VideoPlaylist[] = [] - - for (const playlistJSON of playlistsJSON) { - playlists.push(new VideoPlaylist(playlistJSON, translations)) - } - - return { data: playlists, total } - }) - ) - } - - extractPlaylist (playlist: VideoPlaylistServerModel) { - return this.serverService.getServerLocale() - .pipe(map(translations => new VideoPlaylist(playlist, translations))) - } - - extractVideoPlaylistElements (result: ResultList) { - return this.serverService.getServerLocale() - .pipe( - map(translations => { - const elementsJson = result.data - const total = result.total - const elements: VideoPlaylistElement[] = [] - - for (const elementJson of elementsJson) { - elements.push(new VideoPlaylistElement(elementJson, translations)) - } - - return { total, data: elements } - }) - ) - } - - private doVideosExistInPlaylist (videoIds: number[]): Observable { - const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist' - - let params = new HttpParams() - params = this.restService.addObjectParams(params, { videoIds }) - - return this.authHttp.get(url, { params, headers: { ignoreLoadingBar: '' } }) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } -} -- cgit v1.2.3