diff options
author | Chocobozzz <me@florianbigard.com> | 2020-06-23 14:10:17 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-06-23 16:00:49 +0200 |
commit | 67ed6552b831df66713bac9e672738796128d33f (patch) | |
tree | 59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared/rest | |
parent | 0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff) | |
download | PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.gz PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.zst PeerTube-67ed6552b831df66713bac9e672738796128d33f.zip |
Reorganize client shared modules
Diffstat (limited to 'client/src/app/shared/rest')
-rw-r--r-- | client/src/app/shared/rest/component-pagination.model.ts | 18 | ||||
-rw-r--r-- | client/src/app/shared/rest/index.ts | 4 | ||||
-rw-r--r-- | client/src/app/shared/rest/rest-extractor.service.ts | 109 | ||||
-rw-r--r-- | client/src/app/shared/rest/rest-pagination.ts | 4 | ||||
-rw-r--r-- | client/src/app/shared/rest/rest-table.ts | 105 | ||||
-rw-r--r-- | client/src/app/shared/rest/rest.service.ts | 111 |
6 files changed, 0 insertions, 351 deletions
diff --git a/client/src/app/shared/rest/component-pagination.model.ts b/client/src/app/shared/rest/component-pagination.model.ts deleted file mode 100644 index bcb73ed0f..000000000 --- a/client/src/app/shared/rest/component-pagination.model.ts +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | export interface ComponentPagination { | ||
2 | currentPage: number | ||
3 | itemsPerPage: number | ||
4 | totalItems: number | ||
5 | } | ||
6 | |||
7 | export type ComponentPaginationLight = Omit<ComponentPagination, 'totalItems'> | ||
8 | |||
9 | export function hasMoreItems (componentPagination: ComponentPagination) { | ||
10 | // No results | ||
11 | if (componentPagination.totalItems === 0) return false | ||
12 | |||
13 | // Not loaded yet | ||
14 | if (!componentPagination.totalItems) return true | ||
15 | |||
16 | const maxPage = componentPagination.totalItems / componentPagination.itemsPerPage | ||
17 | return maxPage > componentPagination.currentPage | ||
18 | } | ||
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts deleted file mode 100644 index f00cda2b8..000000000 --- a/client/src/app/shared/rest/index.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export * from './rest-extractor.service' | ||
2 | export * from './rest-pagination' | ||
3 | export * from './rest.service' | ||
4 | export * from './rest-table' | ||
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts deleted file mode 100644 index e6518dd1d..000000000 --- a/client/src/app/shared/rest/rest-extractor.service.ts +++ /dev/null | |||
@@ -1,109 +0,0 @@ | |||
1 | import { throwError as observableThrowError } from 'rxjs' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { dateToHuman } from '@app/shared/misc/utils' | ||
4 | import { ResultList } from '../../../../../shared' | ||
5 | import { Router } from '@angular/router' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | |||
8 | @Injectable() | ||
9 | export class RestExtractor { | ||
10 | |||
11 | constructor ( | ||
12 | private router: Router, | ||
13 | private i18n: I18n | ||
14 | ) { } | ||
15 | |||
16 | extractDataBool () { | ||
17 | return true | ||
18 | } | ||
19 | |||
20 | applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> { | ||
21 | const data: T[] = result.data | ||
22 | const newData: T[] = [] | ||
23 | |||
24 | data.forEach(d => newData.push(fun.apply(this, [ d ].concat(additionalArgs)))) | ||
25 | |||
26 | return { | ||
27 | total: result.total, | ||
28 | data: newData | ||
29 | } | ||
30 | } | ||
31 | |||
32 | convertResultListDateToHuman <T> (result: ResultList<T>, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList<T> { | ||
33 | return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ]) | ||
34 | } | ||
35 | |||
36 | convertDateToHuman (target: { [ id: string ]: string }, fieldsToConvert: string[]) { | ||
37 | fieldsToConvert.forEach(field => target[field] = dateToHuman(target[field])) | ||
38 | |||
39 | return target | ||
40 | } | ||
41 | |||
42 | handleError (err: any) { | ||
43 | let errorMessage | ||
44 | |||
45 | if (err.error instanceof Error) { | ||
46 | // A client-side or network error occurred. Handle it accordingly. | ||
47 | errorMessage = err.error.message | ||
48 | console.error('An error occurred:', errorMessage) | ||
49 | } else if (typeof err.error === 'string') { | ||
50 | errorMessage = err.error | ||
51 | } else if (err.status !== undefined) { | ||
52 | // A server-side error occurred. | ||
53 | if (err.error && err.error.errors) { | ||
54 | const errors = err.error.errors | ||
55 | const errorsArray: string[] = [] | ||
56 | |||
57 | Object.keys(errors).forEach(key => { | ||
58 | errorsArray.push(errors[key].msg) | ||
59 | }) | ||
60 | |||
61 | errorMessage = errorsArray.join('. ') | ||
62 | } else if (err.error && err.error.error) { | ||
63 | errorMessage = err.error.error | ||
64 | } else if (err.status === 413) { | ||
65 | errorMessage = this.i18n( | ||
66 | 'Request is too large for the server. Please contact you administrator if you want to increase the limit size.' | ||
67 | ) | ||
68 | } else if (err.status === 429) { | ||
69 | const secondsLeft = err.headers.get('retry-after') | ||
70 | if (secondsLeft) { | ||
71 | const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60) | ||
72 | errorMessage = this.i18n('Too many attempts, please try again after {{minutesLeft}} minutes.', { minutesLeft }) | ||
73 | } else { | ||
74 | errorMessage = this.i18n('Too many attempts, please try again later.') | ||
75 | } | ||
76 | } else if (err.status === 500) { | ||
77 | errorMessage = this.i18n('Server error. Please retry later.') | ||
78 | } | ||
79 | |||
80 | errorMessage = errorMessage ? errorMessage : 'Unknown error.' | ||
81 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | ||
82 | } else { | ||
83 | console.error(err) | ||
84 | errorMessage = err | ||
85 | } | ||
86 | |||
87 | const errorObj: { message: string, status: string, body: string } = { | ||
88 | message: errorMessage, | ||
89 | status: undefined, | ||
90 | body: undefined | ||
91 | } | ||
92 | |||
93 | if (err.status) { | ||
94 | errorObj.status = err.status | ||
95 | errorObj.body = err.error | ||
96 | } | ||
97 | |||
98 | return observableThrowError(errorObj) | ||
99 | } | ||
100 | |||
101 | redirectTo404IfNotFound (obj: { status: number }, status = [ 404 ]) { | ||
102 | if (obj && obj.status && status.indexOf(obj.status) !== -1) { | ||
103 | // Do not use redirectService to avoid circular dependencies | ||
104 | this.router.navigate([ '/404' ], { skipLocationChange: true }) | ||
105 | } | ||
106 | |||
107 | return observableThrowError(obj) | ||
108 | } | ||
109 | } | ||
diff --git a/client/src/app/shared/rest/rest-pagination.ts b/client/src/app/shared/rest/rest-pagination.ts deleted file mode 100644 index 0faa59303..000000000 --- a/client/src/app/shared/rest/rest-pagination.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export interface RestPagination { | ||
2 | start: number | ||
3 | count: number | ||
4 | } | ||
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts deleted file mode 100644 index d4e6cf5f2..000000000 --- a/client/src/app/shared/rest/rest-table.ts +++ /dev/null | |||
@@ -1,105 +0,0 @@ | |||
1 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' | ||
2 | import { LazyLoadEvent, SortMeta } from 'primeng/api' | ||
3 | import { RestPagination } from './rest-pagination' | ||
4 | import { Subject } from 'rxjs' | ||
5 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | ||
6 | |||
7 | export abstract class RestTable { | ||
8 | |||
9 | abstract totalRecords: number | ||
10 | abstract sort: SortMeta | ||
11 | abstract pagination: RestPagination | ||
12 | |||
13 | search: string | ||
14 | rowsPerPageOptions = [ 10, 20, 50, 100 ] | ||
15 | rowsPerPage = this.rowsPerPageOptions[0] | ||
16 | expandedRows = {} | ||
17 | |||
18 | private searchStream: Subject<string> | ||
19 | |||
20 | abstract getIdentifier (): string | ||
21 | |||
22 | initialize () { | ||
23 | this.loadSort() | ||
24 | this.initSearch() | ||
25 | } | ||
26 | |||
27 | loadSort () { | ||
28 | const result = peertubeLocalStorage.getItem(this.getSortLocalStorageKey()) | ||
29 | |||
30 | if (result) { | ||
31 | try { | ||
32 | this.sort = JSON.parse(result) | ||
33 | } catch (err) { | ||
34 | console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err) | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | loadLazy (event: LazyLoadEvent) { | ||
40 | this.sort = { | ||
41 | order: event.sortOrder, | ||
42 | field: event.sortField | ||
43 | } | ||
44 | |||
45 | this.pagination = { | ||
46 | start: event.first, | ||
47 | count: this.rowsPerPage | ||
48 | } | ||
49 | |||
50 | this.loadData() | ||
51 | this.saveSort() | ||
52 | } | ||
53 | |||
54 | saveSort () { | ||
55 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
56 | } | ||
57 | |||
58 | initSearch () { | ||
59 | this.searchStream = new Subject() | ||
60 | |||
61 | this.searchStream | ||
62 | .pipe( | ||
63 | debounceTime(400), | ||
64 | distinctUntilChanged() | ||
65 | ) | ||
66 | .subscribe(search => { | ||
67 | this.search = search | ||
68 | this.loadData() | ||
69 | }) | ||
70 | } | ||
71 | |||
72 | onSearch (event: Event) { | ||
73 | const target = event.target as HTMLInputElement | ||
74 | this.searchStream.next(target.value) | ||
75 | } | ||
76 | |||
77 | onPage (event: { first: number, rows: number }) { | ||
78 | if (this.rowsPerPage !== event.rows) { | ||
79 | this.rowsPerPage = event.rows | ||
80 | this.pagination = { | ||
81 | start: event.first, | ||
82 | count: this.rowsPerPage | ||
83 | } | ||
84 | this.loadData() | ||
85 | } | ||
86 | this.expandedRows = {} | ||
87 | } | ||
88 | |||
89 | setTableFilter (filter: string) { | ||
90 | // FIXME: cannot use ViewChild, so create a component for the filter input | ||
91 | const filterInput = document.getElementById('table-filter') as HTMLInputElement | ||
92 | if (filterInput) filterInput.value = filter | ||
93 | } | ||
94 | |||
95 | resetSearch () { | ||
96 | this.searchStream.next('') | ||
97 | this.setTableFilter('') | ||
98 | } | ||
99 | |||
100 | protected abstract loadData (): void | ||
101 | |||
102 | private getSortLocalStorageKey () { | ||
103 | return 'rest-table-sort-' + this.getIdentifier() | ||
104 | } | ||
105 | } | ||
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts deleted file mode 100644 index 78558851a..000000000 --- a/client/src/app/shared/rest/rest.service.ts +++ /dev/null | |||
@@ -1,111 +0,0 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { HttpParams } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { ComponentPaginationLight } from './component-pagination.model' | ||
5 | import { RestPagination } from './rest-pagination' | ||
6 | |||
7 | interface QueryStringFilterPrefixes { | ||
8 | [key: string]: { | ||
9 | prefix: string | ||
10 | handler?: (v: string) => string | number | ||
11 | multiple?: boolean | ||
12 | } | ||
13 | } | ||
14 | |||
15 | type ParseQueryStringFilterResult = { | ||
16 | [key: string]: string | number | (string | number)[] | ||
17 | } | ||
18 | |||
19 | @Injectable() | ||
20 | export class RestService { | ||
21 | |||
22 | addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) { | ||
23 | let newParams = params | ||
24 | |||
25 | if (pagination !== undefined) { | ||
26 | newParams = newParams.set('start', pagination.start.toString()) | ||
27 | .set('count', pagination.count.toString()) | ||
28 | } | ||
29 | |||
30 | if (sort !== undefined) { | ||
31 | let sortString = '' | ||
32 | |||
33 | if (typeof sort === 'string') { | ||
34 | sortString = sort | ||
35 | } else { | ||
36 | const sortPrefix = sort.order === 1 ? '' : '-' | ||
37 | sortString = sortPrefix + sort.field | ||
38 | } | ||
39 | |||
40 | newParams = newParams.set('sort', sortString) | ||
41 | } | ||
42 | |||
43 | return newParams | ||
44 | } | ||
45 | |||
46 | addObjectParams (params: HttpParams, object: { [ name: string ]: any }) { | ||
47 | for (const name of Object.keys(object)) { | ||
48 | const value = object[name] | ||
49 | if (value === undefined || value === null) continue | ||
50 | |||
51 | if (Array.isArray(value) && value.length !== 0) { | ||
52 | for (const v of value) params = params.append(name, v) | ||
53 | } else { | ||
54 | params = params.append(name, value) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | return params | ||
59 | } | ||
60 | |||
61 | componentPaginationToRestPagination (componentPagination: ComponentPaginationLight): RestPagination { | ||
62 | const start: number = (componentPagination.currentPage - 1) * componentPagination.itemsPerPage | ||
63 | const count: number = componentPagination.itemsPerPage | ||
64 | |||
65 | return { start, count } | ||
66 | } | ||
67 | |||
68 | parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): ParseQueryStringFilterResult { | ||
69 | if (!q) return {} | ||
70 | |||
71 | // Tokenize the strings using spaces | ||
72 | const tokens = q.split(' ').filter(token => !!token) | ||
73 | |||
74 | // Build prefix array | ||
75 | const prefixeStrings = Object.values(prefixes) | ||
76 | .map(p => p.prefix) | ||
77 | |||
78 | // Search is the querystring minus defined filters | ||
79 | const searchTokens = tokens.filter(t => { | ||
80 | return prefixeStrings.every(prefixString => t.startsWith(prefixString) === false) | ||
81 | }) | ||
82 | |||
83 | const additionalFilters: ParseQueryStringFilterResult = {} | ||
84 | |||
85 | for (const prefixKey of Object.keys(prefixes)) { | ||
86 | const prefixObj = prefixes[prefixKey] | ||
87 | const prefix = prefixObj.prefix | ||
88 | |||
89 | const matchedTokens = tokens.filter(t => t.startsWith(prefix)) | ||
90 | .map(t => t.slice(prefix.length)) // Keep the value filter | ||
91 | .map(t => { | ||
92 | if (prefixObj.handler) return prefixObj.handler(t) | ||
93 | |||
94 | return t | ||
95 | }) | ||
96 | .filter(t => !!t || t === 0) | ||
97 | |||
98 | if (matchedTokens.length === 0) continue | ||
99 | |||
100 | additionalFilters[prefixKey] = prefixObj.multiple === true | ||
101 | ? matchedTokens | ||
102 | : matchedTokens[0] | ||
103 | } | ||
104 | |||
105 | return { | ||
106 | search: searchTokens.join(' ') || undefined, | ||
107 | |||
108 | ...additionalFilters | ||
109 | } | ||
110 | } | ||
111 | } | ||