@@ -50,6 +50,7 @@ import {
5050 usePreferredSpecies ,
5151} from '../hooks/preferredOrganisms' ;
5252import { useReferenceStrains } from '../hooks/referenceStrains' ;
53+ import { useMaxRecommendedGate } from '../hooks/maxRecommendedGate' ;
5354
5455import { OrganismPreferencesWarning } from './OrganismPreferencesWarning' ;
5556
@@ -64,16 +65,20 @@ export const SHOW_ONLY_PREFERRED_ORGANISMS_PROPERTY =
6465export const HIGHLIGHT_REFERENCE_ORGANISMS_PROPERTY =
6566 'highlightReferenceOrganisms' ;
6667export const IS_SPECIES_PARAM_PROPERTY = 'isSpeciesParam' ;
68+ export const MAX_RECOMMENDED_PROPERTY = 'maxRecommended' ;
69+ export const MAX_RECOMMENDED_MSG_PROPERTY = 'maxRecommendedMsg' ;
6770
68- interface OrganismParamProps < T extends Parameter , S = void >
69- extends DefaultParamProps < T , S > {
71+ interface OrganismParamProps <
72+ T extends Parameter ,
73+ S = void ,
74+ > extends DefaultParamProps < T , S > {
7075 isSearchPage ?: boolean ;
7176}
7277
7378export function OrganismParam ( props : OrganismParamProps < Parameter , State > ) {
7479 if ( ! isOrganismParamProps ( props ) ) {
7580 throw new Error (
76- `Tried to render non-organism parameter ${ props . parameter . name } with OrganismParam.`
81+ `Tried to render non-organism parameter ${ props . parameter . name } with OrganismParam.` ,
7782 ) ;
7883 }
7984
@@ -87,7 +92,7 @@ export function OrganismParam(props: OrganismParamProps<Parameter, State>) {
8792}
8893
8994export function ValidatedOrganismParam (
90- props : OrganismParamProps < EnumParam , State >
95+ props : OrganismParamProps < EnumParam , State > ,
9196) {
9297 return props . parameter . displayType === 'treeBox' ? (
9398 < TreeBoxOrganismEnumParam
@@ -101,18 +106,31 @@ export function ValidatedOrganismParam(
101106}
102107
103108function TreeBoxOrganismEnumParam (
104- props : OrganismParamProps < TreeBoxEnumParam , State >
109+ props : OrganismParamProps < TreeBoxEnumParam , State > ,
105110) {
106111 const [ showOnlyReferenceOrganisms , setShowOnlyReferenceOrganisms ] =
107112 useState < boolean > ( false ) ;
108113
109114 const { selectedValues, onChange } = useEnumParamSelectedValues ( props ) ;
110115
116+ // Extract maxRecommended and apply the gate
117+ const maxRecommended = Number (
118+ props . parameter . properties ?. [ MAX_RECOMMENDED_PROPERTY ] ?. [ 0 ] ,
119+ ) ;
120+ const maxRecommendedMsg =
121+ props . parameter . properties ?. [ MAX_RECOMMENDED_MSG_PROPERTY ] ?. [ 0 ] ;
122+
123+ const { wrappedOnChange, modalElement } = useMaxRecommendedGate (
124+ onChange ,
125+ maxRecommended ,
126+ maxRecommendedMsg ,
127+ ) ;
128+
111129 const paramWithPrunedVocab = useTreeBoxParamWithPrunedVocab (
112130 props . parameter ,
113131 selectedValues ,
114- onChange ,
115- props . isSearchPage
132+ wrappedOnChange ,
133+ props . isSearchPage ,
116134 ) ;
117135
118136 const { maxSelectedCount } = paramWithPrunedVocab ;
@@ -121,12 +139,12 @@ function TreeBoxOrganismEnumParam(
121139
122140 const shouldHighlightReferenceOrganisms =
123141 props . parameter . properties ?. [ ORGANISM_PROPERTIES_KEY ] . includes (
124- HIGHLIGHT_REFERENCE_ORGANISMS_PROPERTY
142+ HIGHLIGHT_REFERENCE_ORGANISMS_PROPERTY ,
125143 ) ?? false ;
126144
127145 const renderNode = useRenderOrganismNode (
128146 shouldHighlightReferenceOrganisms ? referenceStrains : undefined ,
129- undefined
147+ undefined ,
130148 ) ;
131149 const searchPredicate = useOrganismSearchPredicate ( referenceStrains ) ;
132150
@@ -176,52 +194,75 @@ function TreeBoxOrganismEnumParam(
176194 searchPredicate ,
177195 showOnlyReferenceOrganisms ,
178196 referenceStrains ,
179- ]
197+ ] ,
180198 ) ;
181199
182- return hasEmptyVocabularly ( paramWithPrunedVocab ) ? (
183- < EmptyParamWarning />
184- ) : (
185- < TreeBoxEnumParamComponent
186- { ...props }
187- selectedValues = { selectedValues }
188- onChange = { onChange }
189- context = { props . ctx }
190- parameter = { paramWithPrunedVocab }
191- wrapCheckboxTreeProps = { wrapCheckboxTreeProps }
192- />
200+ return (
201+ < >
202+ { hasEmptyVocabularly ( paramWithPrunedVocab ) ? (
203+ < EmptyParamWarning />
204+ ) : (
205+ < TreeBoxEnumParamComponent
206+ { ...props }
207+ selectedValues = { selectedValues }
208+ onChange = { wrappedOnChange }
209+ context = { props . ctx }
210+ parameter = { paramWithPrunedVocab }
211+ wrapCheckboxTreeProps = { wrapCheckboxTreeProps }
212+ />
213+ ) }
214+ { modalElement }
215+ </ >
193216 ) ;
194217}
195218
196219function FlatOrganismEnumParam (
197- props : OrganismParamProps < FlatEnumParam , State >
220+ props : OrganismParamProps < FlatEnumParam , State > ,
198221) {
199222 const { selectedValues, onChange } = useEnumParamSelectedValues ( props ) ;
200223
224+ // Extract maxRecommended and apply the gate
225+ const maxRecommended = Number (
226+ props . parameter . properties ?. [ MAX_RECOMMENDED_PROPERTY ] ?. [ 0 ] ,
227+ ) ;
228+ const maxRecommendedMsg =
229+ props . parameter . properties ?. [ MAX_RECOMMENDED_MSG_PROPERTY ] ?. [ 0 ] ;
230+
231+ const { wrappedOnChange, modalElement } = useMaxRecommendedGate (
232+ onChange ,
233+ maxRecommended ,
234+ maxRecommendedMsg ,
235+ ) ;
236+
201237 const paramWithPrunedVocab = useFlatParamWithPrunedVocab (
202238 props . parameter ,
203239 selectedValues ,
204- onChange ,
205- props . isSearchPage
240+ wrappedOnChange ,
241+ props . isSearchPage ,
206242 ) ;
207243
208- return hasEmptyVocabularly ( paramWithPrunedVocab ) ? (
209- < EmptyParamWarning />
210- ) : (
211- < ParamComponent { ...props } parameter = { paramWithPrunedVocab } />
244+ return (
245+ < >
246+ { hasEmptyVocabularly ( paramWithPrunedVocab ) ? (
247+ < EmptyParamWarning />
248+ ) : (
249+ < ParamComponent { ...props } parameter = { paramWithPrunedVocab } />
250+ ) }
251+ { modalElement }
252+ </ >
212253 ) ;
213254}
214255
215256function useTreeBoxParamWithPrunedVocab (
216257 parameter : TreeBoxEnumParam ,
217258 selectedValues : string [ ] ,
218259 onChange : ( newValue : string [ ] ) => void ,
219- isSearchPage ?: boolean
260+ isSearchPage ?: boolean ,
220261) {
221262 const preferredValues = usePreferredValues (
222263 parameter ,
223264 selectedValues ,
224- isSearchPage
265+ isSearchPage ,
225266 ) ;
226267
227268 const [ preferredOrganismsEnabled ] = usePreferredOrganismsEnabledState ( ) ;
@@ -243,7 +284,7 @@ function useTreeBoxParamWithPrunedVocab(
243284 ? pruneDescendantNodes (
244285 ( node ) =>
245286 node . children . length > 0 || preferredValues . has ( node . data . term ) ,
246- prunedVocabulary
287+ prunedVocabulary ,
247288 )
248289 : prunedVocabulary ;
249290
@@ -259,7 +300,7 @@ function useTreeBoxParamWithPrunedVocab(
259300 selectedValues ,
260301 onChange ,
261302 preferredValues ,
262- paramWithPrunedVocab
303+ paramWithPrunedVocab ,
263304 ) ;
264305
265306 return paramWithPrunedVocab ;
@@ -269,12 +310,12 @@ function useFlatParamWithPrunedVocab(
269310 parameter : FlatEnumParam ,
270311 selectedValues : string [ ] ,
271312 onChange : ( newValue : string [ ] ) => void ,
272- isSearchPage ?: boolean
313+ isSearchPage ?: boolean ,
273314) {
274315 const preferredValues = usePreferredValues (
275316 parameter ,
276317 selectedValues ,
277- isSearchPage
318+ isSearchPage ,
278319 ) ;
279320
280321 const [ preferredOrganismsEnabled ] = usePreferredOrganismsEnabledState ( ) ;
@@ -287,7 +328,7 @@ function useFlatParamWithPrunedVocab(
287328 ? {
288329 ...parameter ,
289330 vocabulary : parameter . vocabulary . filter ( ( [ term ] ) =>
290- preferredValues . has ( term )
331+ preferredValues . has ( term ) ,
291332 ) ,
292333 }
293334 : parameter ;
@@ -297,23 +338,23 @@ function useFlatParamWithPrunedVocab(
297338 selectedValues ,
298339 onChange ,
299340 preferredValues ,
300- paramWithPrunedVocab
341+ paramWithPrunedVocab ,
301342 ) ;
302343
303344 return paramWithPrunedVocab ;
304345}
305346
306347function useEnumParamSelectedValues (
307- props : OrganismParamProps < EnumParam , State >
348+ props : OrganismParamProps < EnumParam , State > ,
308349) {
309350 const paramIsMultiPick = isMultiPick ( props . parameter ) ;
310351
311352 const selectedValues = useMemo ( ( ) => {
312353 return paramIsMultiPick
313354 ? toMultiValueArray ( props . value )
314355 : props . value == null || props . value === ''
315- ? [ ]
316- : [ props . value ] ;
356+ ? [ ]
357+ : [ props . value ] ;
317358 } , [ paramIsMultiPick , props . value ] ) ;
318359
319360 const transformValue = useCallback (
@@ -324,7 +365,7 @@ function useEnumParamSelectedValues(
324365 return newValue . length === 0 ? '' : newValue [ 0 ] ;
325366 }
326367 } ,
327- [ paramIsMultiPick ]
368+ [ paramIsMultiPick ] ,
328369 ) ;
329370
330371 const onParamValueChange = props . onParamValueChange ;
@@ -333,7 +374,7 @@ function useEnumParamSelectedValues(
333374 ( newValue : string [ ] ) => {
334375 onParamValueChange ( transformValue ( newValue ) ) ;
335376 } ,
336- [ onParamValueChange , transformValue ]
377+ [ onParamValueChange , transformValue ] ,
337378 ) ;
338379
339380 return {
@@ -345,7 +386,7 @@ function useEnumParamSelectedValues(
345386function usePreferredValues (
346387 parameter : EnumParam ,
347388 selectedValues : string [ ] ,
348- isSearchPageProp ?: boolean
389+ isSearchPageProp ?: boolean ,
349390) {
350391 const [ preferredOrganisms ] = usePreferredOrganismsState ( ) ;
351392 const preferredSpecies = usePreferredSpecies ( ) ;
@@ -372,9 +413,9 @@ function usePreferredValues(
372413 initialSelectedValuesRef . current ,
373414 parameter . vocabulary ,
374415 isSearchPage ,
375- findPreferenceType ( parameter )
416+ findPreferenceType ( parameter ) ,
376417 ) ,
377- [ parameter , isSearchPage , preferredOrganisms , preferredSpecies ]
418+ [ parameter , isSearchPage , preferredOrganisms , preferredSpecies ] ,
378419 ) ;
379420
380421 return preferredValues ;
@@ -384,7 +425,7 @@ function useRestrictSelectedValues(
384425 selectedValues : string [ ] ,
385426 onChange : ( newValue : string [ ] ) => void ,
386427 preferredValues : Set < string > ,
387- parameter : EnumParam
428+ parameter : EnumParam ,
388429) {
389430 const [ preferredOrganismsEnabled ] = usePreferredOrganismsEnabledState ( ) ;
390431
@@ -409,7 +450,7 @@ function useRestrictSelectedValues(
409450 ! hasEmptyVocabularly ( parameter )
410451 ) {
411452 const preferredSelectedValues = selectedValues . filter ( ( selectedValue ) =>
412- preferredValues . has ( selectedValue )
453+ preferredValues . has ( selectedValue ) ,
413454 ) ;
414455
415456 if ( preferredSelectedValues . length !== selectedValues . length ) {
@@ -427,7 +468,7 @@ function useRestrictSelectedValues(
427468}
428469
429470function isOrganismParamProps < S = void > (
430- props : OrganismParamProps < Parameter , S >
471+ props : OrganismParamProps < Parameter , S > ,
431472) : props is OrganismParamProps < EnumParam , S > {
432473 return isPropsType ( props , isOrganismParam ) ;
433474}
@@ -441,7 +482,7 @@ export function isOrganismParam(parameter: Parameter): parameter is EnumParam {
441482
442483function findShouldOnlyShowPreferredOrganisms ( parameter : Parameter ) {
443484 return parameter . properties ?. [ ORGANISM_PROPERTIES_KEY ] . includes (
444- SHOW_ONLY_PREFERRED_ORGANISMS_PROPERTY
485+ SHOW_ONLY_PREFERRED_ORGANISMS_PROPERTY ,
445486 ) ;
446487}
447488
@@ -465,7 +506,7 @@ function findPreferredValues(
465506 selectedValues : string [ ] ,
466507 vocabulary : EnumParam [ 'vocabulary' ] ,
467508 isSearchPage : boolean ,
468- preferenceType : 'organism' | 'species'
509+ preferenceType : 'organism' | 'species' ,
469510) {
470511 const basePreferredValues =
471512 preferenceType === 'organism' ? preferredOrganismValues : preferredSpecies ;
@@ -481,7 +522,7 @@ function findPreferredValues(
481522
482523function findPreferredDescendants (
483524 vocabRoot : TreeBoxVocabNode ,
484- preferredValues : Set < string >
525+ preferredValues : Set < string > ,
485526) {
486527 const preferredDescendants = new Set < string > ( ) ;
487528
0 commit comments