From 693263e936763a851e3c8c020e3739def8bd4eca Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Apr 2019 10:44:18 +0200 Subject: Refactor videos selection components --- client/src/app/shared/angular/from-now.pipe.ts | 40 ++++++++ .../app/shared/angular/number-formatter.pipe.ts | 19 ++++ .../src/app/shared/angular/object-length.pipe.ts | 8 ++ .../shared/angular/peertube-template.directive.ts | 12 +++ client/src/app/shared/misc/from-now.pipe.ts | 40 -------- .../src/app/shared/misc/number-formatter.pipe.ts | 19 ---- client/src/app/shared/misc/object-length.pipe.ts | 8 -- client/src/app/shared/shared.module.ts | 17 +++- client/src/app/shared/video/abstract-video-list.ts | 5 + .../shared/video/videos-selection.component.html | 26 +++++ .../shared/video/videos-selection.component.scss | 57 +++++++++++ .../app/shared/video/videos-selection.component.ts | 112 +++++++++++++++++++++ 12 files changed, 291 insertions(+), 72 deletions(-) create mode 100644 client/src/app/shared/angular/from-now.pipe.ts create mode 100644 client/src/app/shared/angular/number-formatter.pipe.ts create mode 100644 client/src/app/shared/angular/object-length.pipe.ts create mode 100644 client/src/app/shared/angular/peertube-template.directive.ts delete mode 100644 client/src/app/shared/misc/from-now.pipe.ts delete mode 100644 client/src/app/shared/misc/number-formatter.pipe.ts delete mode 100644 client/src/app/shared/misc/object-length.pipe.ts create mode 100644 client/src/app/shared/video/videos-selection.component.html create mode 100644 client/src/app/shared/video/videos-selection.component.scss create mode 100644 client/src/app/shared/video/videos-selection.component.ts (limited to 'client/src/app/shared') diff --git a/client/src/app/shared/angular/from-now.pipe.ts b/client/src/app/shared/angular/from-now.pipe.ts new file mode 100644 index 000000000..3a9a76411 --- /dev/null +++ b/client/src/app/shared/angular/from-now.pipe.ts @@ -0,0 +1,40 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { I18n } from '@ngx-translate/i18n-polyfill' + +// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site +@Pipe({ name: 'myFromNow' }) +export class FromNowPipe implements PipeTransform { + + constructor (private i18n: I18n) { } + + transform (arg: number | Date | string) { + const argDate = new Date(arg) + const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) + + let interval = Math.floor(seconds / 31536000) + if (interval > 1) { + return this.i18n('{{interval}} years ago', { interval }) + } + + interval = Math.floor(seconds / 2592000) + if (interval > 1) return this.i18n('{{interval}} months ago', { interval }) + if (interval === 1) return this.i18n('{{interval}} month ago', { interval }) + + interval = Math.floor(seconds / 604800) + if (interval > 1) return this.i18n('{{interval}} weeks ago', { interval }) + if (interval === 1) return this.i18n('{{interval}} week ago', { interval }) + + interval = Math.floor(seconds / 86400) + if (interval > 1) return this.i18n('{{interval}} days ago', { interval }) + if (interval === 1) return this.i18n('{{interval}} day ago', { interval }) + + interval = Math.floor(seconds / 3600) + if (interval > 1) return this.i18n('{{interval}} hours ago', { interval }) + if (interval === 1) return this.i18n('{{interval}} hour ago', { interval }) + + interval = Math.floor(seconds / 60) + if (interval >= 1) return this.i18n('{{interval}} min ago', { interval }) + + return this.i18n('{{interval}} sec ago', { interval: Math.max(0, seconds) }) + } +} diff --git a/client/src/app/shared/angular/number-formatter.pipe.ts b/client/src/app/shared/angular/number-formatter.pipe.ts new file mode 100644 index 000000000..8a0756a36 --- /dev/null +++ b/client/src/app/shared/angular/number-formatter.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core' + +// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts + +@Pipe({ name: 'myNumberFormatter' }) +export class NumberFormatterPipe implements PipeTransform { + private dictionary: Array<{max: number, type: string}> = [ + { max: 1000, type: '' }, + { max: 1000000, type: 'K' }, + { max: 1000000000, type: 'M' } + ] + + transform (value: number) { + const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1] + const calc = Math.floor(value / (format.max / 1000)) + + return `${calc}${format.type}` + } +} diff --git a/client/src/app/shared/angular/object-length.pipe.ts b/client/src/app/shared/angular/object-length.pipe.ts new file mode 100644 index 000000000..84d182052 --- /dev/null +++ b/client/src/app/shared/angular/object-length.pipe.ts @@ -0,0 +1,8 @@ +import { Pipe, PipeTransform } from '@angular/core' + +@Pipe({ name: 'myObjectLength' }) +export class ObjectLengthPipe implements PipeTransform { + transform (value: Object) { + return Object.keys(value).length + } +} diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/angular/peertube-template.directive.ts new file mode 100644 index 000000000..a514b6057 --- /dev/null +++ b/client/src/app/shared/angular/peertube-template.directive.ts @@ -0,0 +1,12 @@ +import { Directive, Input, TemplateRef } from '@angular/core' + +@Directive({ + selector: '[ptTemplate]' +}) +export class PeerTubeTemplateDirective { + @Input('ptTemplate') name: string + + constructor (public template: TemplateRef) { + // empty + } +} diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/misc/from-now.pipe.ts deleted file mode 100644 index 3a9a76411..000000000 --- a/client/src/app/shared/misc/from-now.pipe.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { I18n } from '@ngx-translate/i18n-polyfill' - -// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site -@Pipe({ name: 'myFromNow' }) -export class FromNowPipe implements PipeTransform { - - constructor (private i18n: I18n) { } - - transform (arg: number | Date | string) { - const argDate = new Date(arg) - const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) - - let interval = Math.floor(seconds / 31536000) - if (interval > 1) { - return this.i18n('{{interval}} years ago', { interval }) - } - - interval = Math.floor(seconds / 2592000) - if (interval > 1) return this.i18n('{{interval}} months ago', { interval }) - if (interval === 1) return this.i18n('{{interval}} month ago', { interval }) - - interval = Math.floor(seconds / 604800) - if (interval > 1) return this.i18n('{{interval}} weeks ago', { interval }) - if (interval === 1) return this.i18n('{{interval}} week ago', { interval }) - - interval = Math.floor(seconds / 86400) - if (interval > 1) return this.i18n('{{interval}} days ago', { interval }) - if (interval === 1) return this.i18n('{{interval}} day ago', { interval }) - - interval = Math.floor(seconds / 3600) - if (interval > 1) return this.i18n('{{interval}} hours ago', { interval }) - if (interval === 1) return this.i18n('{{interval}} hour ago', { interval }) - - interval = Math.floor(seconds / 60) - if (interval >= 1) return this.i18n('{{interval}} min ago', { interval }) - - return this.i18n('{{interval}} sec ago', { interval: Math.max(0, seconds) }) - } -} diff --git a/client/src/app/shared/misc/number-formatter.pipe.ts b/client/src/app/shared/misc/number-formatter.pipe.ts deleted file mode 100644 index 8a0756a36..000000000 --- a/client/src/app/shared/misc/number-formatter.pipe.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts - -@Pipe({ name: 'myNumberFormatter' }) -export class NumberFormatterPipe implements PipeTransform { - private dictionary: Array<{max: number, type: string}> = [ - { max: 1000, type: '' }, - { max: 1000000, type: 'K' }, - { max: 1000000000, type: 'M' } - ] - - transform (value: number) { - const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1] - const calc = Math.floor(value / (format.max / 1000)) - - return `${calc}${format.type}` - } -} diff --git a/client/src/app/shared/misc/object-length.pipe.ts b/client/src/app/shared/misc/object-length.pipe.ts deleted file mode 100644 index 84d182052..000000000 --- a/client/src/app/shared/misc/object-length.pipe.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -@Pipe({ name: 'myObjectLength' }) -export class ObjectLengthPipe implements PipeTransform { - transform (value: Object) { - return Object.keys(value).length - } -} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 3647fc786..68225b457 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -14,10 +14,7 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth' import { ButtonComponent } from './buttons/button.component' import { DeleteButtonComponent } from './buttons/delete-button.component' import { EditButtonComponent } from './buttons/edit-button.component' -import { FromNowPipe } from './misc/from-now.pipe' import { LoaderComponent } from './misc/loader.component' -import { NumberFormatterPipe } from './misc/number-formatter.pipe' -import { ObjectLengthPipe } from './misc/object-length.pipe' import { RestExtractor, RestService } from './rest' import { UserService } from './users' import { VideoAbuseService } from './video-abuse' @@ -78,6 +75,11 @@ import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/vide import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' +import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' +import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' +import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' +import { FromNowPipe } from '@app/shared/angular/from-now.pipe' +import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' @NgModule({ imports: [ @@ -107,6 +109,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli VideoPlaylistMiniatureComponent, VideoAddToPlaylistComponent, VideoPlaylistElementMiniatureComponent, + VideosSelectionComponent, FeedComponent, @@ -114,10 +117,12 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli DeleteButtonComponent, EditButtonComponent, - ActionDropdownComponent, NumberFormatterPipe, ObjectLengthPipe, FromNowPipe, + PeerTubeTemplateDirective, + + ActionDropdownComponent, MarkdownTextareaComponent, InfiniteScrollerDirective, TextareaAutoResizeDirective, @@ -166,6 +171,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli VideoPlaylistMiniatureComponent, VideoAddToPlaylistComponent, VideoPlaylistElementMiniatureComponent, + VideosSelectionComponent, FeedComponent, @@ -197,7 +203,8 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli NumberFormatterPipe, ObjectLengthPipe, - FromNowPipe + FromNowPipe, + PeerTubeTemplateDirective ], providers: [ diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 467f629ea..099650129 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts @@ -102,6 +102,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor ({ videos, totalVideos }) => { this.pagination.totalItems = totalVideos this.videos = this.videos.concat(videos) + + this.onMoreVideos() }, error => this.notifier.error(error.message) @@ -118,6 +120,9 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor throw new Error('toggleModerationDisplay is not implemented') } + // On videos hook for children that want to do something + protected onMoreVideos () { /* empty */ } + protected loadRouteParams (routeParams: { [ key: string ]: any }) { this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort this.categoryOneOf = routeParams[ 'categoryOneOf' ] diff --git a/client/src/app/shared/video/videos-selection.component.html b/client/src/app/shared/video/videos-selection.component.html new file mode 100644 index 000000000..6f3401b4b --- /dev/null +++ b/client/src/app/shared/video/videos-selection.component.html @@ -0,0 +1,26 @@ +
No results.
+ +
+
+
+ +
+ + + + +
+
+ + Cancel + + + +
+
+ + + + +
+
diff --git a/client/src/app/shared/video/videos-selection.component.scss b/client/src/app/shared/video/videos-selection.component.scss new file mode 100644 index 000000000..d3cbabf23 --- /dev/null +++ b/client/src/app/shared/video/videos-selection.component.scss @@ -0,0 +1,57 @@ +@import '_variables'; +@import '_mixins'; + +.action-selection-mode { + display: flex; + justify-content: flex-end; + flex-grow: 1; + + .action-selection-mode-child { + position: fixed; + + .action-button { + display: inline-block; + } + + .action-button-cancel-selection { + @include peertube-button; + @include grey-button; + + margin-right: 10px; + } + } +} + +.video { + @include row-blocks; + + &:first-child { + margin-top: 47px; + } + + .checkbox-container { + display: flex; + align-items: center; + margin-right: 20px; + margin-left: 12px; + } + + my-video-miniature { + flex-grow: 1; + } +} + +@media screen and (max-width: $small-view) { + .video { + flex-direction: column; + height: auto; + + .checkbox-container { + display: none; + } + + my-button { + margin-top: 10px; + } + } +} diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts new file mode 100644 index 000000000..b6bedafd8 --- /dev/null +++ b/client/src/app/shared/video/videos-selection.component.ts @@ -0,0 +1,112 @@ +import { + AfterContentInit, + Component, + ContentChildren, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + QueryList, + TemplateRef +} from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { AbstractVideoList } from '@app/shared/video/abstract-video-list' +import { AuthService, Notifier, ServerService } from '@app/core' +import { ScreenService } from '@app/shared/misc/screen.service' +import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component' +import { Observable } from 'rxjs' +import { Video } from '@app/shared/video/video.model' +import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' +import { VideoSortField } from '@app/shared/video/sort-field.type' + +export type SelectionType = { [ id: number ]: boolean } + +@Component({ + selector: 'my-videos-selection', + templateUrl: './videos-selection.component.html', + styleUrls: [ './videos-selection.component.scss' ] +}) +export class VideosSelectionComponent extends AbstractVideoList implements OnInit, OnDestroy, AfterContentInit { + @Input() titlePage: string + @Input() miniatureDisplayOptions: MiniatureDisplayOptions + @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }> + @ContentChildren(PeerTubeTemplateDirective) templates: QueryList + + @Output() selectionChange = new EventEmitter() + @Output() videosModelChange = new EventEmitter() + + _selection: SelectionType = {} + + rowButtonsTemplate: TemplateRef + globalButtonsTemplate: TemplateRef + + constructor ( + protected router: Router, + protected route: ActivatedRoute, + protected notifier: Notifier, + protected authService: AuthService, + protected screenService: ScreenService, + protected serverService: ServerService + ) { + super() + } + + ngAfterContentInit () { + { + const t = this.templates.find(t => t.name === 'rowButtons') + if (t) this.rowButtonsTemplate = t.template + } + + { + const t = this.templates.find(t => t.name === 'globalButtons') + if (t) this.globalButtonsTemplate = t.template + } + } + + @Input() get selection () { + return this._selection + } + + set selection (selection: SelectionType) { + this._selection = selection + this.selectionChange.emit(this._selection) + } + + @Input() get videosModel () { + return this.videos + } + + set videosModel (videos: Video[]) { + this.videos = videos + this.videosModelChange.emit(this.videos) + } + + ngOnInit () { + super.ngOnInit() + } + + ngOnDestroy () { + super.ngOnDestroy() + } + + getVideosObservable (page: number) { + return this.getVideosObservableFunction(page, this.sort) + } + + abortSelectionMode () { + this._selection = {} + } + + isInSelectionMode () { + return Object.keys(this._selection).some(k => this._selection[ k ] === true) + } + + generateSyndicationList () { + throw new Error('Method not implemented.') + } + + protected onMoreVideos () { + this.videosModel = this.videos + } +} -- cgit v1.2.3