From 28798b5d949826551740fc893d06e6424b77aa6a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 30 Jan 2017 22:41:14 +0100 Subject: [PATCH] Client: replace simple tables by ng2 smart table component --- client/package.json | 1 + .../friend-list/friend-list.component.html | 24 +----- .../friend-list/friend-list.component.ts | 58 ++++++++++---- .../+admin/friends/shared/friend.service.ts | 18 ++--- .../app/+admin/users/shared/user.service.ts | 20 +---- .../users/user-list/user-list.component.html | 25 +------ .../users/user-list/user-list.component.ts | 71 ++++++++++++------ .../video-abuse-list.component.html | 28 +------ .../video-abuse-list.component.ts | 75 ++++++++++++++----- client/src/app/shared/index.ts | 1 + client/src/app/shared/rest/index.ts | 1 + .../src/app/shared/rest/rest-data-source.ts | 51 +++++++++++++ client/src/app/shared/shared.module.ts | 5 +- client/src/app/shared/utils.ts | 12 +++ .../shared/video-abuse/video-abuse.service.ts | 8 +- client/src/sass/application.scss | 23 +++++- server/initializers/constants.js | 4 +- 17 files changed, 263 insertions(+), 162 deletions(-) create mode 100644 client/src/app/shared/rest/rest-data-source.ts create mode 100644 client/src/app/shared/utils.ts diff --git a/client/package.json b/client/package.json index bd6cb03e7..c762a1fd5 100644 --- a/client/package.json +++ b/client/package.json @@ -58,6 +58,7 @@ "ng2-bootstrap": "1.1.16-10", "ng2-file-upload": "^1.1.4-2", "ng2-meta": "https://github.com/chocobozzz/ng2-meta#build", + "ng2-smart-table": "^0.5.1-0", "ngc-webpack": "1.1.0", "node-sass": "^4.1.1", "normalize.css": "^5.0.0", diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html index 06258f8c8..254d0c65e 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.html +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.html @@ -1,29 +1,11 @@

Friends list

- - - - - - - - - + - - - - - - - - -
IDHostScoreCreated Date
{{ friend.id }}{{ friend.host }}{{ friend.score }}{{ friend.createdAt | date: 'medium' }}
- - + Quit friends - + Make friends diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts index 175ad9cba..f29427640 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { NotificationsService } from 'angular2-notifications'; +import { ServerDataSource } from 'ng2-smart-table'; import { ConfirmService } from '../../../core'; +import { Utils } from '../../../shared'; import { Friend, FriendService } from '../shared'; @Component({ @@ -10,17 +12,51 @@ import { Friend, FriendService } from '../shared'; templateUrl: './friend-list.component.html', styleUrls: [ './friend-list.component.scss' ] }) -export class FriendListComponent implements OnInit { - friends: Friend[]; +export class FriendListComponent { + friendsSource = null; + tableSettings = { + attr: { + class: 'table-hover' + }, + hideSubHeader: true, + actions: { + position: 'right', + add: false, + edit: false, + delete: false + }, + columns: { + id: { + title: 'ID', + sort: false, + sortDirection: 'asc' + }, + host: { + title: 'Host', + sort: false + }, + score: { + title: 'Score', + sort: false + }, + createdAt: { + title: 'Created Date', + sort: false, + valuePrepareFunction: Utils.dateToHuman + } + } + } constructor( private notificationsService: NotificationsService, private confirmService: ConfirmService, private friendService: FriendService - ) { } + ) { + this.friendsSource = this.friendService.getDataSource(); + } - ngOnInit() { - this.getFriends(); + hasFriends() { + return this.friendsSource.count() != 0; } quitFriends() { @@ -33,7 +69,7 @@ export class FriendListComponent implements OnInit { status => { this.notificationsService.success('Sucess', 'Friends left!'); - this.getFriends(); + this.friendsSource.refresh(); }, err => this.notificationsService.error('Error', err.text) @@ -41,12 +77,4 @@ export class FriendListComponent implements OnInit { } ); } - - private getFriends() { - this.friendService.getFriends().subscribe( - res => this.friends = res.friends, - - err => this.notificationsService.error('Error', err.text) - ); - } } diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts index e97459385..6cb84f5cd 100644 --- a/client/src/app/+admin/friends/shared/friend.service.ts +++ b/client/src/app/+admin/friends/shared/friend.service.ts @@ -3,8 +3,10 @@ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; +import { ServerDataSource } from 'ng2-smart-table'; + import { Friend } from './friend.model'; -import { AuthHttp, RestExtractor, ResultList } from '../../../shared'; +import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'; @Injectable() export class FriendService { @@ -15,11 +17,8 @@ export class FriendService { private restExtractor: RestExtractor ) {} - getFriends() { - return this.authHttp.get(FriendService.BASE_FRIEND_URL) - .map(this.restExtractor.extractDataList) - .map(this.extractFriends) - .catch((res) => this.restExtractor.handleError(res)); + getDataSource() { + return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL); } makeFriends(notEmptyHosts) { @@ -37,11 +36,4 @@ export class FriendService { .map(res => res.status) .catch((res) => this.restExtractor.handleError(res)); } - - private extractFriends(result: ResultList) { - const friends: Friend[] = result.data; - const totalFriends = result.total; - - return { friends, totalFriends }; - } } diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts index d9005b213..f6d360e09 100644 --- a/client/src/app/+admin/users/shared/user.service.ts +++ b/client/src/app/+admin/users/shared/user.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; -import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared'; +import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'; @Injectable() export class UserService { @@ -25,25 +25,11 @@ export class UserService { .catch(this.restExtractor.handleError); } - getUsers() { - return this.authHttp.get(UserService.BASE_USERS_URL) - .map(this.restExtractor.extractDataList) - .map(this.extractUsers) - .catch((res) => this.restExtractor.handleError(res)); + getDataSource() { + return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL); } removeUser(user: User) { return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); } - - private extractUsers(result: ResultList) { - const usersJson = result.data; - const totalUsers = result.total; - const users = []; - for (const userJson of usersJson) { - users.push(new User(userJson)); - } - - return { users, totalUsers }; - } } diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 36193d119..3d3d7e054 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html @@ -1,26 +1,9 @@

Users list

- - - - - - - - - - - - - - - - - - -
IDUsernameCreated DateRemove
{{ user.id }}{{ user.username }}{{ user.createdAt | date: 'medium' }} - -
+ diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index baefb7064..db025d3a8 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -1,9 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { NotificationsService } from 'angular2-notifications'; import { ConfirmService } from '../../../core'; -import { User } from '../../../shared'; +import { User, Utils } from '../../../shared'; import { UserService } from '../shared'; @Component({ @@ -11,33 +11,62 @@ import { UserService } from '../shared'; templateUrl: './user-list.component.html', styleUrls: [ './user-list.component.scss' ] }) -export class UserListComponent implements OnInit { - totalUsers: number; - users: User[]; +export class UserListComponent { + usersSource = null; + tableSettings = { + mode: 'external', + attr: { + class: 'table-hover' + }, + hideSubHeader: true, + actions: { + position: 'right', + add: false, + edit: false, + delete: true + }, + delete: { + deleteButtonContent: Utils.getRowDeleteButton() + }, + pager: { + display: true, + perPage: 10 + }, + columns: { + id: { + title: 'ID', + sortDirection: 'asc' + }, + username: { + title: 'Username' + }, + role: { + title: 'Role', + sort: false + }, + createdAt: { + title: 'Created Date', + valuePrepareFunction: Utils.dateToHuman + } + } + } constructor( private notificationsService: NotificationsService, private confirmService: ConfirmService, private userService: UserService - ) {} - - ngOnInit() { - this.getUsers(); + ) { + this.usersSource = this.userService.getDataSource(); } - getUsers() { - this.userService.getUsers().subscribe( - ({ users, totalUsers }) => { - this.users = users; - this.totalUsers = totalUsers; - }, + removeUser({ data }) { + const user: User = data; - err => this.notificationsService.error('Error', err.text) - ); - } + if (user.username === 'root') { + this.notificationsService.error('Error', 'You cannot delete root.'); + return; + } - - removeUser(user: User) { this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe( res => { if (res === false) return; @@ -45,7 +74,7 @@ export class UserListComponent implements OnInit { this.userService.removeUser(user).subscribe( () => { this.notificationsService.success('Success', `User ${user.username} deleted.`); - this.getUsers(); + this.usersSource.refresh(); }, err => this.notificationsService.error('Error', err.text) diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html index 46043577c..b2fd17bf0 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html @@ -1,27 +1,5 @@

Video abuses list

- - - - - - - - - - - - - - - - - - - - - - -
IDReasonReporter pod hostReporter usernameVideoCreated at
{{ videoAbuse.id }}{{ videoAbuse.reason }}{{ videoAbuse.reporterPodHost }}{{ videoAbuse.reporterUsername }} - {{ videoAbuse.videoId }} - {{ videoAbuse.createdAt | date: 'medium' }}
+ diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts index cfd9151b0..2f22a4ab0 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts @@ -1,35 +1,72 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { NotificationsService } from 'angular2-notifications'; -import { VideoAbuseService, VideoAbuse} from '../../../shared'; +import { Utils, VideoAbuseService, VideoAbuse} from '../../../shared'; @Component({ selector: 'my-video-abuse-list', templateUrl: './video-abuse-list.component.html', styleUrls: [ './video-abuse-list.component.scss' ] }) -export class VideoAbuseListComponent implements OnInit { - videoAbuses: VideoAbuse[]; +export class VideoAbuseListComponent { + videoAbusesSource = null; + tableSettings = { + mode: 'external', + attr: { + class: 'table-hover' + }, + hideSubHeader: true, + actions: { + position: 'right', + add: false, + edit: false, + delete: false + }, + pager: { + display: true, + perPage: 10 + }, + columns: { + id: { + title: 'ID', + sortDirection: 'asc' + }, + reason: { + title: 'Reason', + sort: false + }, + reporterPodHost: { + title: 'Reporter pod host', + sort: false + }, + reporterUsername: { + title: 'Reporter username', + sort: false + }, + videoId: { + title: 'Video', + type: 'html', + sort: false, + valuePrepareFunction: this.buildVideoLink + }, + createdAt: { + title: 'Created Date', + valuePrepareFunction: Utils.dateToHuman + } + } + } constructor( private notificationsService: NotificationsService, private videoAbuseService: VideoAbuseService - ) { } + ) { + this.videoAbusesSource = this.videoAbuseService.getDataSource(); + } - ngOnInit() { - this.getVideoAbuses(); - } - - buildVideoLink(videoAbuse: VideoAbuse) { - return `/videos/${videoAbuse.videoId}`; - } - - private getVideoAbuses() { - this.videoAbuseService.getVideoAbuses().subscribe( - res => this.videoAbuses = res.videoAbuses, - - err => this.notificationsService.error('Error', err.text) - ); + buildVideoLink(videoId: string) { + // TODO: transform to routerLink + // https://github.com/akveo/ng2-smart-table/issues/57 + return `
${videoId}`; } } diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index 7876e6f14..61e8ed523 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts @@ -5,3 +5,4 @@ export * from './search'; export * from './users'; export * from './video-abuse'; export * from './shared.module'; +export * from './utils'; diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts index 3c9509dc7..3cb123c3b 100644 --- a/client/src/app/shared/rest/index.ts +++ b/client/src/app/shared/rest/index.ts @@ -1,3 +1,4 @@ +export * from './rest-data-source'; export * from './rest-extractor.service'; export * from './rest-pagination'; export * from './rest.service'; diff --git a/client/src/app/shared/rest/rest-data-source.ts b/client/src/app/shared/rest/rest-data-source.ts new file mode 100644 index 000000000..847dd7c56 --- /dev/null +++ b/client/src/app/shared/rest/rest-data-source.ts @@ -0,0 +1,51 @@ +import { Http, RequestOptionsArgs, URLSearchParams, } from '@angular/http'; + +import { ServerDataSource } from 'ng2-smart-table'; + +export class RestDataSource extends ServerDataSource { + constructor(http: Http, endpoint: string) { + const options = { + endPoint: endpoint, + sortFieldKey: 'sort', + dataKey: 'data' + } + + super(http, options); + } + + protected extractTotalFromResponse(res) { + const rawData = res.json(); + return rawData ? parseInt(rawData.total): 0; + } + + protected addSortRequestOptions(requestOptions: RequestOptionsArgs) { + let searchParams: URLSearchParams = requestOptions.search; + + if (this.sortConf) { + this.sortConf.forEach((fieldConf) => { + const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''; + + searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field); + }); + } + + return requestOptions; + } + + protected addPagerRequestOptions(requestOptions: RequestOptionsArgs) { + let searchParams: URLSearchParams = requestOptions.search; + + if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { + const perPage = this.pagingConf['perPage']; + const page = this.pagingConf['page']; + + const start = (page - 1) * perPage; + const count = perPage; + + searchParams.set('start', start.toString()); + searchParams.set('count', count.toString()); + } + + return requestOptions; + } +} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 7b2386d6c..99893c8b1 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -10,6 +10,7 @@ import { ProgressbarModule } from 'ng2-bootstrap/progressbar'; import { PaginationModule } from 'ng2-bootstrap/pagination'; import { ModalModule } from 'ng2-bootstrap/modal'; import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; import { AUTH_HTTP_PROVIDERS } from './auth'; import { RestExtractor, RestService } from './rest'; @@ -29,7 +30,8 @@ import { VideoAbuseService } from './video-abuse'; PaginationModule.forRoot(), ProgressbarModule.forRoot(), - FileUploadModule + FileUploadModule, + Ng2SmartTableModule ], declarations: [ @@ -49,6 +51,7 @@ import { VideoAbuseService } from './video-abuse'; ModalModule, PaginationModule, ProgressbarModule, + Ng2SmartTableModule, BytesPipe, SearchComponent diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts new file mode 100644 index 000000000..1dd6f96f0 --- /dev/null +++ b/client/src/app/shared/utils.ts @@ -0,0 +1,12 @@ +import { DatePipe } from '@angular/common'; + +export class Utils { + + static dateToHuman(date: String) { + return new DatePipe('en').transform(date, 'medium') + } + + static getRowDeleteButton() { + return ''; + } +} diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts index 2750a41c7..f23c36f05 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/video-abuse/video-abuse.service.ts @@ -6,7 +6,7 @@ import 'rxjs/add/operator/map'; import { AuthService } from '../core'; import { AuthHttp } from '../auth'; -import { RestExtractor, ResultList } from '../rest'; +import { RestDataSource, RestExtractor, ResultList } from '../rest'; import { VideoAbuse } from './video-abuse.model'; @Injectable() @@ -18,10 +18,8 @@ export class VideoAbuseService { private restExtractor: RestExtractor ) {} - getVideoAbuses() { - return this.authHttp.get(VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse') - .map(this.restExtractor.extractDataList) - .map(this.extractVideoAbuses) + getDataSource() { + return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse'); } reportVideo(id: string, reason: string) { diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 30588067f..994b1e2b9 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -42,8 +42,23 @@ menu { } } -.table-column-id { - width: 200px; +.ng2-smart-table-container { + .ng2-smart-table { + + thead tr { + border-top: 1px solid rgb(233, 235, 236) + } + + td, th { + padding: 8px !important; + color: #333333 !important; + font-size: 14px !important; + } + } + + .ng2-smart-pagination-nav .page-link { + font-size: 11px !important; + } } [hidden] { @@ -55,6 +70,10 @@ input.readonly { background-color: #fff !important; } +.glyphicon-black { + color: black; +} + footer { border-top: 1px solid rgba(0, 0, 0, 0.2); padding-top: 10px; diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 90adbf406..ad7cf4f4d 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -18,8 +18,8 @@ const SEARCHABLE_COLUMNS = { // Sortable columns per schema const SORTABLE_COLUMNS = { - USERS: [ 'username', '-username', 'createdAt', '-createdAt' ], - VIDEO_ABUSES: [ 'createdAt', '-createdAt' ], + USERS: [ 'id', '-id', 'username', '-username', 'createdAt', '-createdAt' ], + VIDEO_ABUSES: [ 'id', '-id', 'createdAt', '-createdAt' ], VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ] }