1
0
Fork 0

Client: replace simple tables by ng2 smart table component

This commit is contained in:
Chocobozzz 2017-01-30 22:41:14 +01:00
parent 13fc89f4a4
commit 28798b5d94
17 changed files with 263 additions and 162 deletions

View File

@ -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",

View File

@ -1,29 +1,11 @@
<h3>Friends list</h3>
<table class="table table-hover">
<thead>
<tr>
<th class="table-column-id">ID</th>
<th>Host</th>
<th>Score</th>
<th>Created Date</th>
</tr>
</thead>
<ng2-smart-table [settings]="tableSettings" [source]="friendsSource"></ng2-smart-table>
<tbody>
<tr *ngFor="let friend of friends">
<td>{{ friend.id }}</td>
<td>{{ friend.host }}</td>
<td>{{ friend.score }}</td>
<td>{{ friend.createdAt | date: 'medium' }}</td>
</tr>
</tbody>
</table>
<a *ngIf="friends && friends.length !== 0" class="add-user btn btn-danger pull-left" (click)="quitFriends()">
<a *ngIf="hasFriends()" class="add-user btn btn-danger pull-left" (click)="quitFriends()">
Quit friends
</a>
<a *ngIf="friends?.length === 0" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
<a *ngIf="!hasFriends()" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
Make friends
</a>

View File

@ -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)
);
}
}

View File

@ -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 };
}
}

View File

@ -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 };
}
}

View File

@ -1,26 +1,9 @@
<h3>Users list</h3>
<table class="table table-hover">
<thead>
<tr>
<th class="table-column-id">ID</th>
<th>Username</th>
<th>Created Date</th>
<th class="text-right">Remove</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.createdAt | date: 'medium' }}</td>
<td class="text-right">
<span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
</td>
</tr>
</tbody>
</table>
<ng2-smart-table
[settings]="tableSettings" [source]="usersSource"
(delete)="removeUser($event)"
></ng2-smart-table>
<a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
<span class="glyphicon glyphicon-plus"></span>

View File

@ -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)

View File

@ -1,27 +1,5 @@
<h3>Video abuses list</h3>
<table class="table table-hover">
<thead>
<tr>
<th class="cell-id">ID</th>
<th class="cell-reason">Reason</th>
<th>Reporter pod host</th>
<th>Reporter username</th>
<th>Video</th>
<th>Created at</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let videoAbuse of videoAbuses">
<td>{{ videoAbuse.id }}</td>
<td>{{ videoAbuse.reason }}</td>
<td>{{ videoAbuse.reporterPodHost }}</td>
<td>{{ videoAbuse.reporterUsername }}</td>
<td>
<a [routerLink]="buildVideoLink(videoAbuse)" title="Go to video">{{ videoAbuse.videoId }}</a>
</td>
<td>{{ videoAbuse.createdAt | date: 'medium' }}</td>
</tr>
</tbody>
</table>
<ng2-smart-table
[settings]="tableSettings" [source]="videoAbusesSource"
></ng2-smart-table>

View File

@ -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 `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`;
}
}

View File

@ -5,3 +5,4 @@ export * from './search';
export * from './users';
export * from './video-abuse';
export * from './shared.module';
export * from './utils';

View File

@ -1,3 +1,4 @@
export * from './rest-data-source';
export * from './rest-extractor.service';
export * from './rest-pagination';
export * from './rest.service';

View File

@ -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 = <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 = <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;
}
}

View File

@ -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

View File

@ -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 '<span class="glyphicon glyphicon-remove glyphicon-black"></span>';
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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' ]
}