1
- import React from 'react'
1
+ import React , { useCallback , useEffect , useMemo , useState } from 'react'
2
2
import classNames from 'classnames'
3
3
import ms from 'milliseconds'
4
4
import { connect } from 'redux-bundler-react'
@@ -11,166 +11,192 @@ import { sortByProperty } from '../../lib/sort.js'
11
11
12
12
import './PeersTable.css'
13
13
14
- export class PeersTable extends React . Component {
15
- /**
16
- *
17
- * @param {object } props
18
- * @param {Promise<any[]> } props.peerLocationsForSwarm
19
- * @param {string } props.className
20
- * @param {import('i18next').TFunction } props.t
21
- */
22
- constructor ( props ) {
23
- super ( props )
24
-
25
- this . state = {
26
- sortBy : 'latency' ,
27
- sortDirection : SortDirection . ASC ,
28
- peerLocationsForSwarm : [ ]
29
- }
30
-
31
- this . sort = this . sort . bind ( this )
32
- }
33
-
34
- flagRenderer = ( flagCode , isPrivate ) => {
35
- // Check if the OS is Windows to render the flags as SVGs
36
- // Windows doesn't render the flags as emojis ¯\_(ツ)_/¯
37
- const isWindows = window . navigator . appVersion . indexOf ( 'Win' ) !== - 1
38
- return (
14
+ const flagRenderer = ( flagCode , isPrivate ) => {
15
+ // Check if the OS is Windows to render the flags as SVGs
16
+ // Windows doesn't render the flags as emojis ¯\_(ツ)_/¯
17
+ const isWindows = window . navigator . appVersion . indexOf ( 'Win' ) !== - 1
18
+ return (
39
19
< span className = 'f4 pr2' >
40
20
{ isPrivate ? '🤝' : flagCode ? < CountryFlag code = { flagCode } svg = { isWindows } /> : '🌐' }
41
21
</ span >
42
- )
43
- }
22
+ )
23
+ }
44
24
45
- locationCellRenderer = ( { rowData } ) => {
46
- const ref = React . createRef ( )
47
- const location = rowData . isPrivate
48
- ? this . props . t ( 'localNetwork' )
49
- : rowData . location
50
- ? rowData . isNearby
51
- ? < span > { rowData . location } < span className = 'charcoal-muted' > ({ this . props . t ( 'nearby' ) } )</ span > </ span >
52
- : rowData . location
53
- : < span className = 'charcoal-muted fw4' > { this . props . t ( 'app:terms.unknown' ) } </ span >
54
- const value = rowData . location || this . props . t ( 'app:terms.unknown' )
55
- return (
56
- < CopyToClipboard text = { value } onCopy = { ( ) => copyFeedback ( ref , this . props . t ) } >
25
+ const locationCellRenderer = ( t ) => ( { rowData } ) => {
26
+ const ref = React . createRef ( )
27
+ const location = rowData . isPrivate
28
+ ? t ( 'localNetwork' )
29
+ : rowData . location
30
+ ? rowData . isNearby
31
+ ? < span > { rowData . location } < span className = 'charcoal-muted' > ({ t ( 'nearby' ) } )</ span > </ span >
32
+ : rowData . location
33
+ : < span className = 'charcoal-muted fw4' > { t ( 'app:terms.unknown' ) } </ span >
34
+ const value = rowData . location || t ( 'app:terms.unknown' )
35
+ return (
36
+ < CopyToClipboard text = { value } onCopy = { ( ) => copyFeedback ( ref , t ) } >
57
37
< span title = { value } className = 'copyable' ref = { ref } >
58
- { this . flagRenderer ( rowData . flagCode , rowData . isPrivate ) }
38
+ { flagRenderer ( rowData . flagCode , rowData . isPrivate ) }
59
39
{ location }
60
40
</ span >
61
41
</ CopyToClipboard >
62
- )
63
- }
42
+ )
43
+ }
64
44
65
- latencyCellRenderer = ( { cellData, rowData } ) => {
66
- const style = { width : '60px' }
67
- const latency = `${ cellData } ms`
68
- if ( cellData == null ) return ( < span className = 'dib o-40 no-select' style = { style } > -</ span > )
69
- return ( < span className = 'dib no-select' > { latency } </ span > )
70
- }
45
+ const latencyCellRenderer = ( { cellData } ) => {
46
+ const style = { width : '60px' }
47
+ const latency = `${ cellData } ms`
48
+ if ( cellData == null ) return ( < span className = 'dib o-40 no-select' style = { style } > -</ span > )
49
+ return ( < span className = 'dib no-select' > { latency } </ span > )
50
+ }
71
51
72
- peerIdCellRenderer = ( { cellData : peerId } ) => {
73
- const ref = React . createRef ( )
74
- const p2pMultiaddr = `/p2p/${ peerId } `
75
- return (
76
- < CopyToClipboard text = { p2pMultiaddr } onCopy = { ( ) => copyFeedback ( ref , this . props . t ) } >
52
+ const peerIdCellRenderer = ( t ) => ( { cellData : peerId } ) => {
53
+ const ref = React . createRef ( )
54
+ const p2pMultiaddr = `/p2p/${ peerId } `
55
+ return (
56
+ < CopyToClipboard text = { p2pMultiaddr } onCopy = { ( ) => copyFeedback ( ref , t ) } >
77
57
< Cid value = { peerId } identicon ref = { ref } className = 'copyable' />
78
58
</ CopyToClipboard >
79
- )
80
- }
59
+ )
60
+ }
81
61
82
- protocolsCellRenderer = ( { rowData, cellData } ) => {
83
- const ref = React . createRef ( )
84
- const { protocols } = rowData
85
- const title = protocols . split ( ', ' ) . join ( '\n' )
86
- return (
87
- < CopyToClipboard text = { protocols } onCopy = { ( ) => copyFeedback ( ref , this . props . t ) } >
62
+ const protocolsCellRenderer = ( t ) => ( { rowData } ) => {
63
+ const ref = React . createRef ( )
64
+ const { protocols } = rowData
65
+ const title = protocols . split ( ', ' ) . join ( '\n' )
66
+ return (
67
+ < CopyToClipboard text = { protocols } onCopy = { ( ) => copyFeedback ( ref , t ) } >
88
68
< span
89
69
ref = { ref }
90
70
className = 'copyable'
91
71
title = { title } >
92
72
{ protocols . replaceAll ( '[unnamed]' , '🤔' ) }
93
73
</ span >
94
74
</ CopyToClipboard >
95
- )
96
- }
75
+ )
76
+ }
97
77
98
- connectionCellRenderer = ( { rowData } ) => {
99
- const ref = React . createRef ( )
100
- const { address, direction, peerId } = rowData
101
- const p2pMultiaddr = `${ address } /p2p/${ peerId } `
102
- const title = direction != null
103
- ? `${ address } \n(${ renderDirection ( direction , this . props . t ) } )`
104
- : address
78
+ const connectionCellRenderer = ( t ) => ( { rowData } ) => {
79
+ const ref = React . createRef ( )
80
+ const { address, direction, peerId } = rowData
81
+ const p2pMultiaddr = `${ address } /p2p/${ peerId } `
82
+ const title = direction != null
83
+ ? `${ address } \n(${ renderDirection ( direction , t ) } )`
84
+ : address
105
85
106
- return (
107
- < CopyToClipboard text = { p2pMultiaddr } onCopy = { ( ) => copyFeedback ( ref , this . props . t ) } >
86
+ return (
87
+ < CopyToClipboard text = { p2pMultiaddr } onCopy = { ( ) => copyFeedback ( ref , t ) } >
108
88
< abbr
109
89
ref = { ref }
110
90
className = 'copyable'
111
91
title = { title } >
112
92
{ rowData . connection }
113
93
</ abbr >
114
94
</ CopyToClipboard >
115
- )
116
- }
117
-
118
- rowClassRenderer = ( { index } , peers = [ ] ) => {
119
- const { selectedPeers } = this . props
120
- const shouldAddHoverEffect = selectedPeers ?. peerIds ?. includes ( peers [ index ] ?. peerId )
121
-
122
- return classNames ( 'bb b--near-white peersTableItem' , index === - 1 && 'bg-near-white' , shouldAddHoverEffect && 'bg-light-gray' )
123
- }
124
-
125
- sort ( { sortBy, sortDirection } ) {
126
- this . setState ( { sortBy, sortDirection } )
127
- }
95
+ )
96
+ }
128
97
129
- componentWillReceiveProps ( nextProps ) {
130
- if ( nextProps . peerLocationsForSwarm ) {
131
- nextProps . peerLocationsForSwarm ?. then ?. ( ( peerLocationsForSwarm ) => {
132
- if ( peerLocationsForSwarm !== this . state . peerLocationsForSwarm ) {
133
- this . setState ( { peerLocationsForSwarm } )
134
- }
135
- } )
136
- }
137
- }
98
+ const rowClassRenderer = ( { index } , peers = [ ] , selectedPeers ) => {
99
+ const shouldAddHoverEffect = selectedPeers ?. peerIds ?. includes ( peers [ index ] ?. peerId )
138
100
139
- render ( ) {
140
- const { className, t } = this . props
141
- const { sortBy, sortDirection, peerLocationsForSwarm } = this . state
101
+ return classNames ( 'bb b--near-white peersTableItem' , index === - 1 && 'bg-near-white' , shouldAddHoverEffect && 'bg-light-gray' )
102
+ }
142
103
143
- const sortedList = peerLocationsForSwarm . sort ( sortByProperty ( sortBy , sortDirection === SortDirection . ASC ? 1 : - 1 ) )
144
- const tableHeight = 400
104
+ const FilterInput = ( { setFilter, t, filteredCount } ) => {
105
+ return (
106
+ < div className = 'flex items-center justify-between pa2' >
107
+ < input
108
+ className = 'input-reset ba b--black-20 pa2 mb2 db w-100'
109
+ type = 'text'
110
+ placeholder = 'Filter peers'
111
+ onChange = { ( e ) => setFilter ( e . target . value ) }
112
+ />
113
+ { /* Now to display the total number of peers filtered out on the right side of the inside of the input */ }
114
+ < div className = 'f4 charcoal-muted absolute top-1 right-1' > { filteredCount } </ div >
115
+ </ div >
116
+ )
117
+ }
145
118
146
- return (
147
- < div className = { `bg-white-70 center ${ className } ` } style = { { height : `${ tableHeight } px` , maxWidth : 1764 } } >
148
- { peerLocationsForSwarm && < AutoSizer disableHeight >
119
+ export const PeersTable = ( { className, t, peerLocationsForSwarm, selectedPeers } ) => {
120
+ const tableHeight = 400
121
+ const [ awaitedPeerLocationsForSwarm , setAwaitedPeerLocationsForSwarm ] = useState ( [ ] )
122
+ const [ sortBy , setSortBy ] = useState ( 'latency' )
123
+ const [ sortDirection , setSortDirection ] = useState ( SortDirection . ASC )
124
+ const [ filter , setFilter ] = useState ( '' )
125
+
126
+ const sort = useCallback ( ( { sortBy, sortDirection } ) => {
127
+ setSortBy ( sortBy )
128
+ setSortDirection ( sortDirection )
129
+ } , [ ] )
130
+ const filterCb = useCallback ( ( value ) => {
131
+ setFilter ( value )
132
+ } , [ ] )
133
+
134
+ useEffect ( ( ) => {
135
+ peerLocationsForSwarm ?. then ?. ( ( peerLocationsForSwarm ) => {
136
+ setAwaitedPeerLocationsForSwarm ( peerLocationsForSwarm )
137
+ } )
138
+ } , [ peerLocationsForSwarm ] )
139
+
140
+ const filteredPeerList = useMemo ( ( ) => {
141
+ const filterLower = filter . toLowerCase ( )
142
+ if ( filterLower === '' ) return awaitedPeerLocationsForSwarm
143
+ return awaitedPeerLocationsForSwarm . filter ( ( { location, latency, peerId, connection, protocols } ) => {
144
+ if ( location != null && location . toLowerCase ( ) . includes ( filterLower ) ) {
145
+ return true
146
+ }
147
+ if ( latency != null && [ latency , `${ latency } ms` ] . some ( ( str ) => str . toString ( ) . includes ( filterLower ) ) ) {
148
+ return true
149
+ }
150
+ if ( peerId != null && peerId . toString ( ) . includes ( filter ) ) {
151
+ return true
152
+ }
153
+ console . log ( 'connection: ' , connection )
154
+ if ( connection != null && connection . toLowerCase ( ) . includes ( filterLower ) ) {
155
+ return true
156
+ }
157
+ if ( protocols != null && protocols . toLowerCase ( ) . includes ( filterLower ) ) {
158
+ return true
159
+ }
160
+
161
+ return false
162
+ } )
163
+ } , [ awaitedPeerLocationsForSwarm , filter ] )
164
+
165
+ const sortedList = useMemo (
166
+ ( ) => filteredPeerList . sort ( sortByProperty ( sortBy , sortDirection === SortDirection . ASC ? 1 : - 1 ) ) ,
167
+ [ filteredPeerList , sortBy , sortDirection ]
168
+ )
169
+
170
+ return (
171
+ < div className = { `bg-white-70 center ${ className } ` } style = { { height : `${ tableHeight } px` , maxWidth : 1764 } } >
172
+ < FilterInput setFilter = { filterCb } t = { t } filteredCount = { sortedList . length } />
173
+ { awaitedPeerLocationsForSwarm && < AutoSizer disableHeight >
149
174
{ ( { width } ) => (
150
- < Table
151
- className = 'tl fw4 w-100 f6'
152
- headerClassName = 'teal fw2 ttu tracked ph2 no-select'
153
- rowClassName = { ( rowInfo ) => this . rowClassRenderer ( rowInfo , peerLocationsForSwarm ) }
154
- width = { width }
155
- height = { tableHeight }
156
- headerHeight = { 32 }
157
- rowHeight = { 36 }
158
- rowCount = { peerLocationsForSwarm . length }
159
- rowGetter = { ( { index } ) => sortedList [ index ] }
160
- sort = { this . sort }
161
- sortBy = { sortBy }
162
- sortDirection = { sortDirection } >
163
- < Column label = { t ( 'app:terms.location' ) } cellRenderer = { this . locationCellRenderer } dataKey = 'location' width = { 450 } className = 'f6 charcoal truncate pl2' />
164
- < Column label = { t ( 'app:terms.latency' ) } cellRenderer = { this . latencyCellRenderer } dataKey = 'latency' width = { 200 } className = 'f6 charcoal pl2' />
165
- < Column label = { t ( 'app:terms.peerId' ) } cellRenderer = { this . peerIdCellRenderer } dataKey = 'peerId' width = { 250 } className = 'charcoal monospace truncate f6 pl2' />
166
- < Column label = { t ( 'app:terms.connection' ) } cellRenderer = { this . connectionCellRenderer } dataKey = 'connection' width = { 250 } className = 'f6 charcoal truncate pl2' />
167
- < Column label = { t ( 'protocols' ) } cellRenderer = { this . protocolsCellRenderer } dataKey = 'protocols' width = { 520 } className = 'charcoal monospace truncate f7 pl2' />
168
- </ Table >
175
+ < >
176
+ < Table
177
+ className = 'tl fw4 w-100 f6'
178
+ headerClassName = 'teal fw2 ttu tracked ph2 no-select'
179
+ rowClassName = { ( rowInfo ) => rowClassRenderer ( rowInfo , awaitedPeerLocationsForSwarm , selectedPeers ) }
180
+ width = { width }
181
+ height = { tableHeight }
182
+ headerHeight = { 32 }
183
+ rowHeight = { 36 }
184
+ rowCount = { sortedList . length }
185
+ rowGetter = { ( { index } ) => sortedList [ index ] }
186
+ sort = { sort }
187
+ sortBy = { sortBy }
188
+ sortDirection = { sortDirection } >
189
+ < Column label = { t ( 'app:terms.location' ) } cellRenderer = { locationCellRenderer ( t ) } dataKey = 'location' width = { 450 } className = 'f6 charcoal truncate pl2' />
190
+ < Column label = { t ( 'app:terms.latency' ) } cellRenderer = { latencyCellRenderer } dataKey = 'latency' width = { 200 } className = 'f6 charcoal pl2' />
191
+ < Column label = { t ( 'app:terms.peerId' ) } cellRenderer = { peerIdCellRenderer ( t ) } dataKey = 'peerId' width = { 250 } className = 'charcoal monospace truncate f6 pl2' />
192
+ < Column label = { t ( 'app:terms.connection' ) } cellRenderer = { connectionCellRenderer ( t ) } dataKey = 'connection' width = { 250 } className = 'f6 charcoal truncate pl2' />
193
+ < Column label = { t ( 'protocols' ) } cellRenderer = { protocolsCellRenderer ( t ) } dataKey = 'protocols' width = { 520 } className = 'charcoal monospace truncate f7 pl2' />
194
+ </ Table >
195
+ </ >
169
196
) }
170
197
</ AutoSizer > }
171
198
</ div >
172
- )
173
- }
199
+ )
174
200
}
175
201
176
202
// API returns integer atm, but that may change in the future
0 commit comments