Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
cccfb001
Commit
cccfb001
authored
Jan 24, 2022
by
Lauri Himanen
Committed by
Markus Scheidgen
Jan 24, 2022
Browse files
Resolve "GUI updates"
parent
11858c6b
Changes
19
Hide whitespace changes
Inline
Side-by-side
gui/src/components/datatable/Datatable.js
View file @
cccfb001
...
...
@@ -333,7 +333,6 @@ const DatatableRow = React.memo(function DatatableRow({data, selected, uncollaps
const
numberOfColumns
=
columns
.
length
+
(
withSelectionFeature
?
1
:
0
)
+
(
actions
?
1
:
0
)
const
handleRowCollapseChange
=
(
event
)
=>
{
event
.
stopPropagation
()
onRowUncollapsed
(
uncollapsed
?
null
:
row
)
}
...
...
gui/src/components/entry/properties/GeometryOptimizationCard.js
View file @
cccfb001
...
...
@@ -24,30 +24,34 @@ import GeometryOptimization from '../../visualization/GeometryOptimization'
export
default
function
GeometryOptimizationCard
({
index
,
archive
,
properties
})
{
const
units
=
useUnits
()
const
geoOptProps
=
index
?.
results
?.
properties
?.
geometry_optimization
// Find out which properties are present
const
hasGeometryOptimization
=
properties
.
has
(
'
geometry_optimization
'
)
// Find out which properties are present. If only one step is calculated
// (n_calculations == 1 and none of the convergence criteria are available),
// the card will not be displayed.
const
hasEnergies
=
properties
.
has
(
'
geometry_optimization
'
)
&&
index
?.
results
?.
properties
?.
n_calculations
>
1
const
hasConvergence
=
properties
.
has
(
'
geometry_optimization
'
)
&&
(
index
?.
results
?.
properties
?.
geometry_optimization
?.
final_energy_difference
||
index
?.
results
?.
properties
?.
geometry_optimization
?.
final_displacement_maximum
||
index
?.
results
?.
properties
?.
geometry_optimization
?.
final_force_maximum
)
// Do not show the card if none of the properties are available, or if only
// one step is calculated.
if
(
!
hasGeometryOptimization
)
{
// Do not show the card if none of the properties are available
if
(
!
hasEnergies
&&
!
hasConvergence
)
{
return
null
}
// Resolve energies
let
energies
=
has
GeometryOptimization
?
null
:
false
const
geoOptProp
sArchive
=
archive
?.
results
?.
properties
?.
geometry_optimization
if
(
has
GeometryOptimization
&&
a
rchive
)
{
energies
=
resolveRef
(
geoOptPropsArchive
.
energies
,
archive
)
let
energies
=
has
Energies
?
null
:
false
const
energie
sArchive
=
archive
?.
results
?.
properties
?.
geometry_optimization
?.
energies
if
(
has
Energies
&&
energiesA
rchive
)
{
energies
=
resolveRef
(
energies
Archive
,
archive
)
}
// Resolve convergence properties
let
convergence
=
false
const
geoOptProps
=
index
?.
results
?.
properties
?.
geometry_optimization
const
geoOptMethod
=
index
.
results
?.
method
?.
simulation
?.
geometry_optimization
if
(
hasGeometryOptimization
)
{
convergence
=
{...
geoOptMethod
,
...
geoOptProps
}
}
let
convergence
=
hasConvergence
?
geoOptProps
:
false
return
<
PropertyCard
title
=
"
Geometry optimization
"
>
<
GeometryOptimization
...
...
gui/src/components/nav/Routes.js
View file @
cccfb001
...
...
@@ -33,7 +33,7 @@ import UploadsPage, { help as uploadsHelp } from '../uploads/UploadsPage'
import
UserdataPage
,
{
help
as
userdataHelp
}
from
'
../UserdataPage
'
import
APIs
from
'
../APIs
'
import
SearchPageEntries
,
{
help
as
searchEntriesHelp
}
from
'
../search/SearchPageEntries
'
import
SearchPageMaterials
,
{
help
as
searchMaterialsHelp
}
from
'
../search/SearchPageMaterials
'
//
import SearchPageMaterials, {help as searchMaterialsHelp} from '../search/SearchPageMaterials'
import
{
aitoolkitEnabled
,
appBase
,
oasis
,
encyclopediaBase
}
from
'
../../config
'
import
EntryQuery
from
'
../entry/EntryQuery
'
import
ResolvePID
from
'
../entry/ResolvePID
'
...
...
@@ -244,18 +244,23 @@ export const routes = [
routes
:
entryRoutes
},
{
path
:
'
materials
'
,
exact
:
true
,
cache
:
'
always
'
,
component
:
SearchPageMaterials
,
menu
:
'
Material Encyclopedia
'
,
tooltip
:
'
Search materials
'
,
breadcrumb
:
'
Materials search
'
,
help
:
{
title
:
'
Searching for materials
'
,
content
:
searchMaterialsHelp
}
href
:
'
https://nomad-lab.eu/prod/rae/encyclopedia
'
,
tooltip
:
'
Search materials in the NOMAD Encyclopedia
'
}
// {
// path: 'materials',
// exact: true,
// cache: 'always',
// component: SearchPageMaterials,
// menu: 'Material Encyclopedia',
// tooltip: 'Search materials',
// breadcrumb: 'Materials search',
// help: {
// title: 'Searching for materials',
// content: searchMaterialsHelp
// }
// }
]
},
{
...
...
gui/src/components/search/FilterRegistry.js
View file @
cccfb001
...
...
@@ -18,7 +18,7 @@
import
{
isNil
}
from
'
lodash
'
import
{
setToArray
,
getDatatype
,
getSerializer
,
getDeserializer
,
getLabel
}
from
'
../../utils
'
import
searchQuantities
from
'
../../searchQuantities
'
import
{
getDimension
}
from
'
../../units
'
import
{
getDimension
,
Quantity
}
from
'
../../units
'
import
InputList
from
'
./input/InputList
'
import
InputPeriodicTable
from
'
./input/InputPeriodicTable
'
import
elementData
from
'
../../elementData
'
...
...
@@ -76,6 +76,10 @@ export const labelWorkflow = 'Workflow'
* As a shortcut you can provide an ES aggregation config as a string,
* e.g. "terms".
* - aggDefaultSize: The default aggregation size, may be overridden.
* - minOverride: Used to override the minimum value from a min_max
* aggregation for this field. Use SI units.
* - maxOverride: Used to override the maximum value from a min_max
* aggregation for this field. Use SI units.
* - value: Object containing a custom setter/getter for the filter value.
* - multiple: Whether the user can simultaneously provide multiple values for
* this filter.
...
...
@@ -130,6 +134,21 @@ function saveFilter(name, group, config) {
data
.
stats
=
config
.
stats
data
.
options
=
config
.
options
data
.
unit
=
config
.
unit
||
searchQuantities
[
name
]?.
unit
data
.
minOverride
=
config
.
minOverride
data
.
maxOverride
=
config
.
maxOverride
if
(
data
.
unit
)
{
const
unitDimension
=
getDimension
(
data
.
unit
)
if
(
data
.
minOverride
&&
unitDimension
!==
getDimension
(
data
.
minOverride
.
unit
))
{
console
.
log
(
unitDimension
)
console
.
log
(
getDimension
(
data
.
minOverride
.
unit
))
throw
Error
(
'
The dimension for minOverride and the filter unit do not match.
'
)
}
if
(
data
.
maxOverride
&&
unitDimension
!==
getDimension
(
data
.
maxOverride
.
unit
))
{
console
.
log
(
unitDimension
)
console
.
log
(
getDimension
(
data
.
maxOverride
.
unit
))
throw
Error
(
'
The dimension for maxOverride and the filter unit do not match.
'
)
}
}
data
.
dtype
=
config
.
dtype
||
getDatatype
(
name
)
data
.
serializerExact
=
getSerializer
(
data
.
dtype
,
false
)
data
.
serializerPretty
=
getSerializer
(
data
.
dtype
,
true
)
...
...
@@ -339,7 +358,7 @@ registerFilter(
nestedQuantity
,
[
{
name
:
'
type
'
,
...
termQuantity
},
{
name
:
'
value
'
,
...
rangeQuantity
}
{
name
:
'
value
'
,
minOverride
:
new
Quantity
(
0
,
'
electron_volt
'
),
...
rangeQuantity
}
]
)
registerFilter
(
...
...
@@ -373,9 +392,9 @@ registerFilter(
labelWorkflow
,
nestedQuantity
,
[
{
name
:
'
final_energy_difference
'
,
...
rangeQuantity
},
{
name
:
'
final_displacement_maximum
'
,
...
rangeQuantity
},
{
name
:
'
final_force_maximum
'
,
...
rangeQuantity
}
{
name
:
'
final_energy_difference
'
,
maxOverride
:
new
Quantity
(
0.1
,
'
electron_volt
'
),
...
rangeQuantity
},
{
name
:
'
final_displacement_maximum
'
,
maxOverride
:
new
Quantity
(
1
,
'
angstrom
'
),
...
rangeQuantity
},
{
name
:
'
final_force_maximum
'
,
maxOverride
:
new
Quantity
(
1
E
-
6
,
'
newton
'
),
...
rangeQuantity
}
]
)
...
...
gui/src/components/search/Search.js
View file @
cccfb001
...
...
@@ -110,10 +110,12 @@ const Search = React.memo(({
<
Box
marginBottom
=
{
2
}
>
<
SearchBar
className
=
{
styles
.
searchBar
}
/
>
<
/Box
>
<
Box
marginBottom
=
{
2
}
>
<
Box
marginBottom
=
{
2
}
position
=
"
relative
"
zIndex
=
{
0
}
>
<
StatisticsGrid
/>
<
/Box
>
<
SearchResults
/>
<
Box
position
=
"
relative
"
zIndex
=
{
1
}
>
<
SearchResults
/>
<
/Box
>
<
div
className
=
{
clsx
(
styles
.
shadow
,
isMenuOpen
&&
styles
.
shadowVisible
)}
><
/div
>
<
/Box
>
<
/div
>
...
...
gui/src/components/search/SearchContext.js
View file @
cccfb001
...
...
@@ -689,9 +689,61 @@ export const SearchContext = React.memo(({
const
[
pagination
,
setPagination
]
=
useRecoilState
(
paginationState
)
const
updateQueryString
=
useUpdateQueryString
()
// All of the heavier pre-processing, checking, etc. should be done in this
// function, as it is the final one that gets called after the debounce
// interval.
/**
* This function is used to sync up API calls so that they update the search
* context state in the same order as they were originally issued.
*
* As we cannot guarantee the order in which the API calls finish, we push all
* calls into a queue. The queue makes sure that API calls get resolved in the
* original order not matter how long the actual call takes.
*/
const
resolve
=
useCallback
(
prop
=>
{
const
{
response
,
timestamp
,
queryChanged
,
paginationChanged
,
search
,
aggsToUpdate
,
resource
,
callback
}
=
prop
const
data
=
response
.
response
let
next
=
apiQueue
.
current
[
0
]
if
(
next
!==
timestamp
)
{
apiMap
.
current
[
timestamp
]
=
prop
return
}
// Update the aggregations if new aggregation data is received. The old
// aggregation data is preserved and new information is updated.
if
(
!
isEmpty
(
data
.
aggregations
))
{
const
newAggs
=
toGUIAgg
(
data
.
aggregations
,
aggsToUpdate
,
resource
)
callback
&&
callback
(
newAggs
)
updateAggsResponse
(
newAggs
)
}
else
{
callback
&&
callback
(
null
)
}
// Update the query results if new data is received.
if
(
queryChanged
||
paginationChanged
)
{
const
isExtend
=
search
.
pagination
.
page_after_value
paginationResponse
.
current
=
data
.
pagination
setResults
(
old
=>
{
const
newResults
=
old
?
{...
old
}
:
{}
isExtend
?
newResults
.
data
=
[...
newResults
.
data
,
...
data
.
data
]
:
newResults
.
data
=
data
.
data
newResults
.
pagination
=
combinePagination
(
search
.
pagination
,
data
.
pagination
)
newResults
.
setPagination
=
setPagination
return
newResults
})
}
// Remove this query from queue and see if next can be resolved.
apiQueue
.
current
.
shift
()
setApiData
(
response
)
const
nextTimestamp
=
apiQueue
.
current
[
0
]
const
nextResolve
=
apiMap
.
current
[
nextTimestamp
]
if
(
nextResolve
)
{
resolve
(
nextResolve
)
}
},
[
setApiData
,
setPagination
,
setResults
,
updateAggsResponse
])
/**
* Function that preprocesses API call requests and finally performs the
* actual API call.
*
* All of the heavier pre-processing, checking, etc. should be done in this
* function, as it is the final one that gets called after the debounce
* interval.
*/
const
apiCall
=
useCallback
((
query
,
aggs
,
pagination
,
queryChanged
,
paginationChanged
,
updateAggs
,
refresh
=
false
,
callback
=
undefined
)
=>
{
// Create the final search object.
const
aggsToUpdate
=
Object
.
keys
(
aggs
).
filter
(
key
=>
aggs
[
key
].
update
)
...
...
@@ -750,48 +802,6 @@ export const SearchContext = React.memo(({
oldQuery
.
current
=
query
oldPagination
.
current
=
pagination
// As we cannot guarantee the order in which the API calls finish, we push
// all calls into a queue. The API calls are always made instantly, but the
// queue makes sure that API calls get resolved in the original order not
// matter how long the actual call takes.
function
resolve
(
prop
)
{
const
{
response
,
timestamp
,
queryChanged
,
paginationChanged
,
search
,
resource
,
callback
}
=
prop
const
data
=
response
.
response
let
next
=
apiQueue
.
current
[
0
]
if
(
next
!==
timestamp
)
{
apiMap
.
current
[
timestamp
]
=
prop
return
}
// Update the aggregations if new aggregation data is received. The old
// aggregation data is preserved and new information is updated.
if
(
!
isEmpty
(
data
.
aggregations
))
{
const
newAggs
=
toGUIAgg
(
data
.
aggregations
,
aggsToUpdate
,
resource
)
callback
&&
callback
(
newAggs
)
updateAggsResponse
(
newAggs
)
}
else
{
callback
&&
callback
(
null
)
}
// Update the query results if new data is received.
if
(
queryChanged
||
paginationChanged
)
{
const
isExtend
=
search
.
pagination
.
page_after_value
paginationResponse
.
current
=
data
.
pagination
setResults
(
old
=>
{
const
newResults
=
old
?
{...
old
}
:
{}
isExtend
?
newResults
.
data
=
[...
newResults
.
data
,
...
data
.
data
]
:
newResults
.
data
=
data
.
data
newResults
.
pagination
=
combinePagination
(
search
.
pagination
,
data
.
pagination
)
newResults
.
setPagination
=
setPagination
return
newResults
})
}
// Remove this query from queue and see if next can be resolved.
apiQueue
.
current
.
shift
()
setApiData
(
response
)
const
nextTimestamp
=
apiQueue
.
current
[
0
]
const
nextResolve
=
apiMap
.
current
[
nextTimestamp
]
if
(
nextResolve
)
{
resolve
(
nextResolve
)
}
}
const
timestamp
=
Date
.
now
()
apiQueue
.
current
.
push
(
timestamp
)
api
.
query
(
resource
,
search
,
{
loadingIndicator
:
true
,
returnRequest
:
true
})
...
...
@@ -802,6 +812,7 @@ export const SearchContext = React.memo(({
queryChanged
,
paginationChanged
,
search
,
aggsToUpdate
,
resource
,
callback
})
...
...
@@ -810,14 +821,17 @@ export const SearchContext = React.memo(({
raiseError
(
error
)
callback
&&
callback
(
undefined
,
error
)
})
},
[
filterDefaults
,
resource
,
api
,
raiseError
,
updateAggsResponse
,
setResults
,
setApiData
,
setPagination
])
},
[
filterDefaults
,
resource
,
api
,
raiseError
,
resolve
])
// This is a debounced version of apiCall.
const
apiCallDebounced
=
useCallback
(
debounce
(
apiCall
,
400
),
[])
// Intermediate function that ensures that:
// - Calls are debounced when necessary
// - API calls are made only if necessary
/**
* Intermediate function that should primarily be used when trying to perform
* an API call. Ensures that ensures that:
* - Calls are debounced when necessary
* - API calls are made only if necessary
*/
const
apiCallInterMediate
=
useCallback
((
query
,
aggs
,
pagination
,
refresh
=
false
,
callback
=
undefined
)
=>
{
if
(
disableUpdate
.
current
)
{
disableUpdate
.
current
=
false
...
...
gui/src/components/search/input/InputCheckbox.js
View file @
cccfb001
...
...
@@ -26,7 +26,6 @@ import {
import
PropTypes
from
'
prop-types
'
import
{
isNil
}
from
'
lodash
'
import
clsx
from
'
clsx
'
import
searchQuantities
from
'
../../../searchQuantities
'
import
{
useSearchContext
}
from
'
../SearchContext
'
const
useStyles
=
makeStyles
(
theme
=>
({
...
...
@@ -50,14 +49,14 @@ const InputCheckbox = React.memo(({
})
=>
{
const
theme
=
useTheme
()
const
styles
=
useStyles
({
classes
:
classes
,
theme
:
theme
})
const
{
filterData
,
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
{
filterData
,
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
[
filter
,
setFilter
]
=
useFilterState
(
quantity
)
const
locked
=
useFilterLocked
(
quantity
)
// Determine the description and units
const
def
=
searchQuantities
[
quantity
]
const
desc
=
isNil
(
description
)
?
(
def
?.
description
||
''
)
:
description
const
title
=
isNil
(
label
)
?
def
?.
name
:
label
const
def
=
filterData
[
quantity
]
const
desc
Final
=
description
||
def
?.
description
||
''
const
labelFinal
=
label
||
def
?.
label
const
disabled
=
locked
const
handleChange
=
useCallback
((
event
,
value
)
=>
{
...
...
@@ -65,7 +64,7 @@ const InputCheckbox = React.memo(({
},
[
setFilter
])
return
<
div
className
=
{
clsx
(
className
,
styles
.
root
)}
data
-
testid
=
{
testID
}
>
<
Tooltip
title
=
{
desc
}
>
<
Tooltip
title
=
{
desc
Final
}
>
<
FormControlLabel
control
=
{
<
Checkbox
color
=
"
primary
"
...
...
@@ -73,7 +72,7 @@ const InputCheckbox = React.memo(({
checked
=
{
isNil
(
filter
)
?
(
isNil
(
initialValue
)
?
filterData
[
quantity
].
default
:
initialValue
)
:
filter
}
onChange
=
{
handleChange
}
/>
}
label
=
{
<
Typography
>
{
title
}
<
/Typography>
}
label
=
{
<
Typography
>
{
labelFinal
}
<
/Typography>
}
/>
<
/Tooltip
>
<
/div
>
...
...
@@ -103,7 +102,6 @@ const useInputCheckboxValueStyles = makeStyles(theme => ({
}))
export
const
InputCheckboxValue
=
React
.
memo
(({
quantity
,
label
,
description
,
value
,
className
,
...
...
@@ -112,13 +110,13 @@ export const InputCheckboxValue = React.memo(({
})
=>
{
const
theme
=
useTheme
()
const
styles
=
useInputCheckboxValueStyles
({
classes
:
classes
,
theme
:
theme
})
const
{
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
{
filterData
,
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
[
filter
,
setFilter
]
=
useFilterState
(
quantity
)
const
locked
=
useFilterLocked
(
quantity
)
// Determine the description and units
const
def
=
searchQuantities
[
quantity
]
const
desc
=
isNil
(
description
)
?
(
def
?.
description
||
''
)
:
description
const
def
=
filterData
[
quantity
]
const
desc
Final
=
description
||
def
?.
description
||
''
const
disabled
=
locked
const
handleChange
=
useCallback
(()
=>
{
...
...
@@ -130,7 +128,7 @@ export const InputCheckboxValue = React.memo(({
},
[
setFilter
,
value
])
return
<
div
className
=
{
clsx
(
className
,
styles
.
root
)}
data
-
testid
=
{
testID
}
>
<
Tooltip
title
=
{
desc
}
>
<
Tooltip
title
=
{
desc
Final
}
>
<
Checkbox
disabled
=
{
disabled
}
color
=
"
primary
"
...
...
@@ -145,7 +143,6 @@ export const InputCheckboxValue = React.memo(({
InputCheckboxValue
.
propTypes
=
{
quantity
:
PropTypes
.
string
.
isRequired
,
label
:
PropTypes
.
string
,
description
:
PropTypes
.
string
,
value
:
PropTypes
.
any
,
className
:
PropTypes
.
string
,
...
...
gui/src/components/search/input/InputField.js
View file @
cccfb001
...
...
@@ -107,7 +107,11 @@ const InputField = React.memo(({
if
(
isArray
(
metainfoOptions
)
&&
metainfoOptions
.
length
>
0
)
{
const
opt
=
{}
for
(
const
name
of
metainfoOptions
)
{
opt
[
name
]
=
{
label
:
name
}
// We do not display the option for 'not processed': it is more of a
// debug value
if
(
name
!==
'
not processed
'
)
{
opt
[
name
]
=
{
label
:
name
}
}
}
return
opt
}
...
...
@@ -267,7 +271,7 @@ const InputField = React.memo(({
reservedHeight
=
itemHeight
+
actionHeight
}
const
total
=
agg
?
Math
.
max
(...
agg
.
data
.
map
(
option
=>
option
.
count
))
:
0
const
max
=
agg
?
Math
.
max
(...
agg
.
data
.
map
(
option
=>
option
.
count
))
:
0
const
items
=
visibleOptions
&&
<
div
className
=
{
styles
.
grid
}
style
=
{{
gridTemplateRows
:
`repeat(
${
nRows
}
, 1fr)`
}}
...
...
@@ -281,7 +285,7 @@ const InputField = React.memo(({
disabled
=
{
value
.
disabled
}
onChange
=
{
handleChange
}
variant
=
"
checkbox
"
total
=
{
total
}
max
=
{
max
}
count
=
{
value
.
count
}
scale
=
{
scale
}
/
>
...
...
gui/src/components/search/input/InputItem.js
View file @
cccfb001
...
...
@@ -83,7 +83,7 @@ const InputItem = React.memo(({
disabled
,
tooltip
,
variant
,
total
,
max
,
count
,
scale
,
disableStatistics
,
...
...
@@ -114,7 +114,7 @@ const InputItem = React.memo(({
const
labelComponent
=
<
div
className
=
{
styles
.
container
}
>
{(
isStatisticsEnabled
&&
!
disableStatistics
)
&&
<
StatisticsBar
className
=
{
styles
.
bar
}
max
=
{
total
}
max
=
{
max
}
value
=
{
count
}
scale
=
{
scale
}
selected
=
{
selected
}
...
...
@@ -171,7 +171,7 @@ InputItem.propTypes = {
disabled
:
PropTypes
.
bool
,
// Whether the option should be disabled
tooltip
:
PropTypes
.
string
,
// Tooltip that is shown for label
variant
:
PropTypes
.
oneOf
([
'
radio
'
,
'
checkbox
'
]),
// The type of item to display
total
:
PropTypes
.
number
,
//
Total number
for statistics
max
:
PropTypes
.
number
,
//
Maximum
for statistics
count
:
PropTypes
.
number
,
// Count of these values for statistics
scale
:
PropTypes
.
number
,
// Scaling of the statistics
disableStatistics
:
PropTypes
.
bool
,
// Use to disable statistics for this item
...
...
gui/src/components/search/input/InputList.js
View file @
cccfb001
...
...
@@ -22,7 +22,6 @@ import PropTypes from 'prop-types'
import
{
isNil
}
from
'
lodash
'
import
{
useResizeDetector
}
from
'
react-resize-detector
'
import
clsx
from
'
clsx
'
import
searchQuantities
from
'
../../../searchQuantities
'
import
InputHeader
from
'
./InputHeader
'
import
InputTooltip
from
'
./InputTooltip
'
import
InputItem
,
{
inputItemHeight
}
from
'
./InputItem
'
...
...
@@ -90,7 +89,7 @@ const InputList = React.memo(({
'
data-testid
'
:
testID
})
=>
{
const
theme
=
useTheme
()
const
{
useAgg
,
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
{
filterData
,
useAgg
,
useFilterState
,
useFilterLocked
}
=
useSearchContext
()
const
styles
=
useStyles
({
classes
:
classes
,
theme
:
theme
})
const
[
scale
,
setScale
]
=
useState
(
initialScale
)
const
[
filter
,
setFilter
]
=
useFilterState
(
quantity
)
...
...
@@ -98,12 +97,12 @@ const InputList = React.memo(({
const
{
height
,
ref
}
=
useResizeDetector
()
const
aggSize
=
useMemo
(()
=>
Math
.
floor
(
height
/
inputItemHeight
),
[
height
])
const
agg
=
useAgg
(
quantity
,
!
isNil
(
height
)
&&
visible
,
aggSize
,
aggId
)
const
total
=
agg
?
Math
.
max
(...
agg
.
data
.
map
(
option
=>
option
.
count
))
:
0
const
max
=
agg
?
Math
.
max
(...
agg
.
data
.
map
(
option
=>
option
.
count
))
:
0
// Determine the description and units
const
def
=
searchQuantities
[
quantity
]
const
desc
=
description
||
def
?.
description
||
''
const
title
=
label
||
def
?.
name
const
def
=
filterData
[
quantity
]
const
desc
Final
=
description
||
def
?.
description
||
''
const
labelFinal
=
label
||
def
?.
label
const
handleChange
=
useCallback
((
event
,
key
,
selected
)
=>
{
setFilter
(
old
=>
{
...
...
@@ -129,7 +128,7 @@ const InputList = React.memo(({
key
=
{
option
.
value
}
value
=
{
option
.
value
}
selected
=
{
filter
?
filter
.
has
(
option
.
value
)
:
false
}
total
=
{
total
}
max
=
{
max
}
onChange
=
{
handleChange
}
variant
=
"
checkbox
"
count
=
{
option
.
count
}
...
...
@@ -143,14 +142,14 @@ const InputList = React.memo(({
component
=
<
InputUnavailable
/>
}
return
[
component
,
index
]
},
[
agg
,
aggSize
,
filter
,
handleChange
,
scale
,
locked
,
total
])
},
[
agg
,
aggSize
,
filter
,
handleChange
,
scale
,
locked
,
max
])
return
<
InputTooltip
locked
=
{
locked
}
>
<
div
className
=
{
clsx
(
className
,
styles
.
root
)}
data
-
testid
=
{
testID
}
>
<
InputHeader
quantity
=
{
quantity
}
label
=
{
title
}
description
=
{
desc
}
label
=
{
labelFinal
}
description
=
{
desc
Final
}
scale
=
{
scale
}
onChangeScale
=
{
setScale
}
draggable
=
{
draggable
}
...
...
gui/src/components/search/input/InputPeriodicTable.js
View file @
cccfb001
...
...
@@ -31,7 +31,6 @@ import InputHeader from './InputHeader'
import
AspectRatio
from
'
../../visualization/AspectRatio
'
import
{
makeStyles
}
from
'
@material-ui/core/styles
'
import
{
useSearchContext
}
from
'
../SearchContext
'
import
searchQuantities
from
'
../../../searchQuantities
'
import
{
approxInteger
}
from
'
../../../utils
'
// A fixed 2D, 10x18 array for the element data.
...
...
@@ -53,8 +52,7 @@ const useElementStyles = makeStyles(theme => ({
bottom
:
1
,
left
:
1
,
right
:
1
,
position
:
'
absolute
'
,
backgroundColor
:
theme
.
palette
.
secondary
.
veryLight
position
:
'
absolute
'
},
fit
:
{
top
:
0
,
...
...
@@ -137,7 +135,7 @@ const Element = React.memo(({
selected
,
disabled
,
onClick
,
total
,
max
,
count
,
scale
,
localFilter
...
...
@@ -150,10 +148,10 @@ const Element = React.memo(({
const
scaler
=
useMemo
(()
=>
scalePow
()
.
exponent
(
scale
)
.
domain
([
0
,
1
])
.
range
([
0
,
1
])
.
range
([
0
.1
,
1
])
// Note that the range should not start from 0
,
[
scale
])
const
finalCount
=
useMemo
(()
=>
approxInteger
(
count
||
0
),
[
count
])
const
finalScale
=
useMemo
(()
=>
scaler
(
count
/
total
)
||
0
,
[
count
,
total
,
scaler
])
const
finalScale
=
useMemo
(()
=>
scaler
(
count
/
max
)
||
0
,
[
count
,
max
,
scaler
])
// Dynamically calculated styles. The background color is formed by animating
// opacity: opacity animation can be GPU-accelerated by the browser unlike
...
...
@@ -161,7 +159,7 @@ const Element = React.memo(({
const
useDynamicStyles
=
makeStyles
((
theme
)
=>
{
return
{
bg
:
{
opacity
:
isStatisticsEnabled
?
(
isNil
(
count
)
||
isNil
(
total
))
?
(
isNil
(
count
)
||
isNil
(
max
))
?
0
:
finalScale
:
0.4
},
...
...
@@ -238,7 +236,7 @@ Element.propTypes = {
onClick
:
PropTypes
.
func
,
selected
:
PropTypes
.
bool
,
disabled
:
PropTypes
.
bool
,
total
:
PropTypes
.
number
,
max
:
PropTypes
.
number
,
count
:
PropTypes
.
number
,
scale
:
PropTypes
.
number
,