4
4
5
5
import React , { PureComponent } from 'react' ;
6
6
import PropTypes from 'prop-types' ;
7
- import { uniqBy , get , countBy } from 'lodash' ;
7
+ import { uniqBy , get , countBy , isNil , xor } from 'lodash' ;
8
8
9
9
10
10
import { CheckboxGroupInputField } from './CheckboxGroupInputField.jsx' ;
11
11
import { CollapsiblePanel } from '../ui/panel/CollapsiblePanel.jsx' ;
12
12
import FieldGroupUtils , { getFieldVal } from '../fieldGroup/FieldGroupUtils.js' ;
13
13
import { dispatchComponentStateChange } from '../core/ComponentCntlr.js' ;
14
+ import { updateSet } from '../util/WebUtil.js' ;
14
15
15
16
import './ImageSelect.css' ;
16
17
@@ -19,17 +20,22 @@ export class ImageSelect extends PureComponent {
19
20
20
21
constructor ( props ) {
21
22
super ( props ) ;
22
- this . state = { lastMod :new Date ( ) . getTime ( ) , filteredImageData :undefined } ; // can be open, close, or auto.
23
+ this . state = { lastMod :new Date ( ) . getTime ( ) } ;
24
+ props . addChangeListener && props . addChangeListener ( 'ImageSelect' , fieldsReducer ( props . imageMasterData ) ) ;
23
25
}
24
26
25
27
render ( ) {
26
28
const { style, imageMasterData, groupKey} = this . props ;
29
+ imageMasterData . forEach ( ( d ) => {
30
+ [ 'missionId' , 'project' , 'subProject' ] . forEach ( ( k ) => d [ k ] = d [ k ] || '' ) ;
31
+ } ) ;
32
+
27
33
var { filteredImageData= imageMasterData } = this . state ;
28
34
const filterMission = toFilterSelectAry ( groupKey , 'mission' ) ;
29
35
const filterProjectType = toFilterSelectAry ( groupKey , 'projectType' ) ;
30
36
const filterwaveBand = toFilterSelectAry ( groupKey , 'waveBand' ) ;
31
37
32
- filteredImageData = filterMission . length > 0 ? filteredImageData . filter ( ( d ) => filterMission . includes ( d . project ) ) : filteredImageData ;
38
+ filteredImageData = filterMission . length > 0 ? filteredImageData . filter ( ( d ) => filterMission . includes ( d . missionId ) ) : filteredImageData ;
33
39
filteredImageData = filterProjectType . length > 0 ? filteredImageData . filter ( ( d ) => filterProjectType . includes ( d . projectTypeKey ) ) : filteredImageData ;
34
40
filteredImageData = filterwaveBand . length > 0 ? filteredImageData . filter ( ( d ) => filterwaveBand . includes ( d . wavelength ) ) : filteredImageData ;
35
41
@@ -42,10 +48,47 @@ export class ImageSelect extends PureComponent {
42
48
}
43
49
44
50
}
51
+ function fieldsReducer ( imageMasterData = { } ) {
52
+ return ( inFields , action ) => {
53
+ const { fieldKey= '' , options= { } , value= '' } = action . payload ;
54
+
55
+ if ( fieldKey . startsWith ( 'PROJ_ALL_' ) ) {
56
+ // a project checkbox is clicked
57
+ const proj = fieldKey . replace ( 'PROJ_ALL_' , '' ) ;
58
+ Object . entries ( inFields ) . forEach ( ( [ k , f ] ) => {
59
+ if ( k . startsWith ( `IMAGES_${ proj } ` ) ) {
60
+ if ( value === '_all_' ) {
61
+ const allVals = f . options ? f . options . map ( ( o ) => o . value ) . join ( ) : '' ;
62
+ inFields = updateSet ( inFields , [ k , 'value' ] , allVals ) ;
63
+ } else {
64
+ inFields = updateSet ( inFields , [ k , 'value' ] , '' ) ;
65
+ }
66
+ }
67
+ } ) ;
68
+ } else if ( fieldKey . startsWith ( 'IMAGES_' ) ) {
69
+ // one item changed, update project selectAll checkbox
70
+ const matcher = fieldKey . split ( '||' ) [ 0 ] ;
71
+ const cbGroups = Object . values ( inFields ) . filter ( ( f ) => f . fieldKey . startsWith ( matcher ) ) ; // array of subproject in this project
72
+ const allSelected = cbGroups . reduce ( ( p , f ) => {
73
+ const selAry = get ( f , 'value' , '' ) . split ( ',' ) ;
74
+ const allAry = get ( f , 'options' , [ ] ) . map ( ( o ) => o . value ) ;
75
+ return p && xor ( selAry , allAry ) . length === 0 ;
76
+ } , true ) ;
77
+ const proj = matcher . substring ( 7 ) ;
78
+ inFields = updateSet ( inFields , [ 'PROJ_ALL_' + proj , 'value' ] , ( allSelected ? '_all_' : '' ) ) ;
79
+ }
80
+
81
+ return inFields ;
82
+ } ;
83
+ }
84
+
45
85
46
86
ImageSelect . propTypes = {
47
- imageMasterData : PropTypes . arrayOf ( PropTypes . object ) ,
48
- groupKey : PropTypes . string ,
87
+ imageMasterData : PropTypes . arrayOf ( PropTypes . object ) . isRequired ,
88
+ groupKey : PropTypes . string . isRequired ,
89
+ // this component needs to be wrapped by a FieldGroup. User of this component need to provide
90
+ // a function so this component can handle field change even from reducing function.
91
+ addChangeListener : PropTypes . func . isRequired ,
49
92
style : PropTypes . object ,
50
93
title : PropTypes . string
51
94
} ;
@@ -67,9 +110,10 @@ function FilterPanel({imageMasterData, title='4. Select Data Set', onChange}) {
67
110
68
111
// eslint-disable-next-line
69
112
function FilterPanelView ( { onChange, imageMasterData} ) {
70
- const missions = toCatSummary ( imageMasterData , 'project' , 'project' ) ;
71
- const projectTypes = toCatSummary ( imageMasterData , 'projectTypeKey' , 'projectTypeDesc' ) ;
72
- const waveBands = toCatSummary ( imageMasterData , 'wavelength' , 'wavelengthDesc' ) ;
113
+ const forSummary = uniqBy ( imageMasterData , ( t ) => `${ t . missionId } ;${ t . project } ` ) ;
114
+ const missions = toFilterSummary ( forSummary , 'missionId' , 'missionId' ) ;
115
+ const projectTypes = toFilterSummary ( forSummary , 'projectTypeKey' , 'projectTypeDesc' ) ;
116
+ const waveBands = toFilterSummary ( forSummary , 'wavelength' , 'wavelengthDesc' ) ;
73
117
74
118
return (
75
119
< div className = 'FilterPanel__view' >
@@ -159,7 +203,7 @@ function FilterSelectExpanded({selections, onClose}) {
159
203
}
160
204
161
205
const toFilterOptions = ( a ) => a . map ( ( d ) => ( { label : `${ d . label } (${ d . count } )` , value : d . name } ) ) ;
162
- const toCatSummary = ( master , key , desc ) => Object . entries ( countBy ( master , ( d ) => `${ get ( d , key ) } ;${ get ( d , desc ) } ` ) )
206
+ const toFilterSummary = ( master , key , desc ) => Object . entries ( countBy ( master , ( d ) => `${ get ( d , key ) } ;${ get ( d , desc ) } ` ) )
163
207
. map ( ( [ d , c ] ) => ( { name : d . split ( ';' ) [ 0 ] , label : d . split ( ';' ) [ 1 ] , count : c } ) ) ;
164
208
165
209
@@ -201,7 +245,7 @@ function DataProduct({groupKey, project, filteredImageData}) {
201
245
202
246
return (
203
247
< div className = 'DataProductList__item' >
204
- < CollapsiblePanel componentKey = { project } header = { project } isOpen = { isOpen } >
248
+ < CollapsiblePanel componentKey = { project } header = { < Header { ... { project} } /> } isOpen = { isOpen } >
205
249
< div className = 'DataProductList__item--details' >
206
250
{
207
251
subProjects . map ( ( sp ) =>
@@ -215,6 +259,26 @@ function DataProduct({groupKey, project, filteredImageData}) {
215
259
216
260
}
217
261
262
+ function Header ( { project} ) {
263
+ const fieldKey = `PROJ_ALL_${ project } ` ;
264
+ return (
265
+ < div className = 'DataProductList__item--header' onClick = { ( e ) => e . stopPropagation ( ) } >
266
+ < CheckboxGroupInputField
267
+ key = { fieldKey }
268
+ fieldKey = { fieldKey }
269
+ initialState = { {
270
+ value : '' , // workaround for _all_ for now
271
+ tooltip : 'Please select some boxes' ,
272
+ label : '' } }
273
+ options = { [ { label :project , value :'_all_' } ] }
274
+ alignment = 'horizontal'
275
+ labelWidth = { 35 }
276
+ wrapperStyle = { { whiteSpace : 'normal' } }
277
+ />
278
+ </ div >
279
+ ) ;
280
+ }
281
+
218
282
const hasImageSelection = ( groupKey , proj ) => {
219
283
const fields = FieldGroupUtils . getGroupFields ( groupKey ) || { } ;
220
284
return Object . values ( fields ) . some ( ( fld ) => fld . fieldKey . startsWith ( `IMAGES_${ proj } ` ) && fld . value ) ;
@@ -223,17 +287,24 @@ const hasImageSelection = (groupKey, proj) => {
223
287
224
288
// eslint-disable-next-line
225
289
function BandSelect ( { subProject, projectData, labelMaxWidth} ) {
226
- const fieldKey = `IMAGES_${ get ( projectData , [ 0 , 'project' ] ) } _${ subProject } ` ;
290
+ subProject = isNil ( subProject ) ? '' : subProject ;
291
+ const fieldKey = `IMAGES_${ get ( projectData , [ 0 , 'project' ] ) } ||${ subProject } ` ;
227
292
const options = toImageOptions ( projectData . filter ( ( p ) => p . subProject === subProject ) ) ;
228
- const label = subProject && < div style = { { minWidth : labelMaxWidth + 'ch' } } className = 'DataProductList__item--bandLabel' > { subProject + ':' } </ div > ;
293
+ const label = subProject && (
294
+ < div style = { { display : 'inline-flex' } } >
295
+ < div style = { { width : labelMaxWidth + 1 + 'ch' } } title = { subProject }
296
+ className = 'DataProductList__item--bandLabel' > { subProject } </ div >
297
+ < span > :</ span >
298
+ </ div > ) ;
229
299
return (
230
300
< div className = 'DataProductList__item--band' >
231
301
{ label }
232
302
< CheckboxGroupInputField
233
303
key = { fieldKey }
234
304
fieldKey = { fieldKey }
235
305
initialState = { {
236
- value : '' , // workaround for _all_ for now
306
+ options, // Note: values in initialState are saved into fieldgroup. options are used in the reducer above to determine what 'all' means.
307
+ value : '' ,
237
308
tooltip : 'Please select some boxes' ,
238
309
label : '' } }
239
310
options = { options }
0 commit comments