@@ -232,6 +232,82 @@ function findClosestInstalledTool(
232232 return ranked [ 0 ] ?. tool ?? null ;
233233}
234234
235+ function ReportFiltersModal ( {
236+ filter, setFilterMode, gpuFilterCounts,
237+ archFilter, setArchFilter, availArchitectures, tierFiltered,
238+ configFilter, setConfigFilterMode, configFilterCounts,
239+ visibleReports,
240+ onClose,
241+ } : {
242+ filter : FilterTier ;
243+ setFilterMode : ( f : FilterTier ) => void ;
244+ gpuFilterCounts : Record < FilterTier , number > ;
245+ archFilter : FilterArch ;
246+ setArchFilter : ( a : FilterArch ) => void ;
247+ availArchitectures : string [ ] ;
248+ tierFiltered : DisplayReportCard [ ] ;
249+ configFilter : ConfigFilter ;
250+ setConfigFilterMode : ( f : ConfigFilter ) => void ;
251+ configFilterCounts : Record < ConfigFilter , number > ;
252+ visibleReports : DisplayReportCard [ ] ;
253+ onClose : ( ) => void ;
254+ } ) {
255+ const rowStyle : React . CSSProperties = { display : 'flex' , alignItems : 'center' , gap : 10 } ;
256+ const labelStyle : React . CSSProperties = { fontSize : 11 , color : '#cfe2f4' , fontWeight : 700 , width : 100 , flexShrink : 0 } ;
257+ return (
258+ < ConfirmModal
259+ strTitle = "Filters"
260+ strOKButtonText = { t ( ) . common . close }
261+ onOK = { onClose }
262+ onCancel = { onClose }
263+ >
264+ < div style = { { display : 'flex' , flexDirection : 'column' , gap : 14 , minWidth : 360 } } >
265+ < div style = { rowStyle } >
266+ < div style = { labelStyle } > { t ( ) . configManager . gpuFilter } </ div >
267+ < div style = { { flex : 1 } } >
268+ < Dropdown
269+ strDefaultLabel = { gpuFilterOptionLabel ( filter , gpuFilterCounts [ filter ] ) }
270+ rgOptions = { FILTER_ORDER . map ( ( tier ) => ( { data : tier , label : gpuFilterOptionLabel ( tier , gpuFilterCounts [ tier ] ) } ) ) }
271+ selectedOption = { filter }
272+ onChange = { ( opt ) => setFilterMode ( opt . data as FilterTier ) }
273+ />
274+ </ div >
275+ </ div >
276+ { availArchitectures . length > 1 && (
277+ < div style = { rowStyle } >
278+ < div style = { labelStyle } > Architecture</ div >
279+ < div style = { { flex : 1 } } >
280+ < Dropdown
281+ strDefaultLabel = { archFilter === 'all' ? `All (${ tierFiltered . length } )` : `${ archFilter } (${ visibleReports . length } )` }
282+ rgOptions = { [
283+ { data : 'all' , label : `All (${ tierFiltered . length } )` } ,
284+ ...availArchitectures . map ( arch => ( {
285+ data : arch ,
286+ label : `${ arch } (${ tierFiltered . filter ( r => r . gpuArchitecture === arch ) . length } )` ,
287+ } ) ) ,
288+ ] }
289+ selectedOption = { archFilter }
290+ onChange = { ( opt ) => setArchFilter ( opt . data as FilterArch ) }
291+ />
292+ </ div >
293+ </ div >
294+ ) }
295+ < div style = { rowStyle } >
296+ < div style = { labelStyle } > { t ( ) . configManager . configFilter } </ div >
297+ < div style = { { flex : 1 } } >
298+ < Dropdown
299+ strDefaultLabel = { configFilterOptionLabel ( configFilter , configFilterCounts [ configFilter ] ) }
300+ rgOptions = { CONFIG_FILTER_ORDER . map ( ( value ) => ( { data : value , label : configFilterOptionLabel ( value , configFilterCounts [ value ] ) } ) ) }
301+ selectedOption = { configFilter }
302+ onChange = { ( opt ) => setConfigFilterMode ( opt . data as ConfigFilter ) }
303+ />
304+ </ div >
305+ </ div >
306+ </ div >
307+ </ ConfirmModal >
308+ ) ;
309+ }
310+
235311function MissingVersionModal ( {
236312 requiredVersion,
237313 latestInstalledLabel,
@@ -1377,7 +1453,6 @@ function ConfigureTabContent({ appId, appName, sysInfo }: Props) {
13771453 { t ( ) . common . shown ( sortedReports . length ) }
13781454 </ div >
13791455 </ div >
1380- { /* flow-children="horizontal" makes dpad left/right move between the dropdowns */ }
13811456 < Focusable
13821457 flow-children = "horizontal"
13831458 style = { {
@@ -1390,9 +1465,7 @@ function ConfigureTabContent({ appId, appName, sysInfo }: Props) {
13901465 borderBottom : '1px solid #2a3a4a' ,
13911466 } }
13921467 >
1393- < div
1394- style = { { fontSize : 10 , color : '#cfe2f4' , fontWeight : 700 , whiteSpace : 'nowrap' , textAlign : 'right' } }
1395- >
1468+ < div style = { { fontSize : 10 , color : '#cfe2f4' , fontWeight : 700 , whiteSpace : 'nowrap' } } >
13961469 { t ( ) . common . sort }
13971470 </ div >
13981471 < Dropdown
@@ -1404,53 +1477,36 @@ function ConfigureTabContent({ appId, appName, sysInfo }: Props) {
14041477 selectedOption = { sortMode }
14051478 onChange = { ( opt ) => setSortPreference ( opt . data as SortMode ) }
14061479 />
1407- < div
1408- style = { { fontSize : 10 , color : '#cfe2f4' , fontWeight : 700 , whiteSpace : 'nowrap' , textAlign : 'right' } }
1409- >
1410- { t ( ) . configManager . gpuFilter }
1411- </ div >
1412- < Dropdown
1413- strDefaultLabel = { gpuFilterOptionLabel ( filter , gpuFilterCounts [ filter ] ) }
1414- rgOptions = { FILTER_ORDER . map ( ( tier ) => ( {
1415- data : tier ,
1416- label : gpuFilterOptionLabel ( tier , gpuFilterCounts [ tier ] ) ,
1417- } ) ) }
1418- selectedOption = { filter }
1419- onChange = { ( opt ) => setFilterMode ( opt . data as FilterTier ) }
1420- />
1421- { availArchitectures . length > 1 && ( < >
1422- < div
1423- style = { { fontSize : 10 , color : '#cfe2f4' , fontWeight : 700 , whiteSpace : 'nowrap' , textAlign : 'right' } }
1424- >
1425- Architecture
1426- </ div >
1427- < Dropdown
1428- strDefaultLabel = { archFilter === 'all' ? `All (${ tierFiltered . length } )` : `${ archFilter } (${ visibleReports . length } )` }
1429- rgOptions = { [
1430- { data : 'all' , label : `All (${ tierFiltered . length } )` } ,
1431- ...availArchitectures . map ( arch => ( {
1432- data : arch ,
1433- label : `${ arch } (${ tierFiltered . filter ( r => r . gpuArchitecture === arch ) . length } )` ,
1434- } ) ) ,
1435- ] }
1436- selectedOption = { archFilter }
1437- onChange = { ( opt ) => setArchFilter ( opt . data as FilterArch ) }
1438- />
1439- </ > ) }
1440- < div
1441- style = { { fontSize : 10 , color : '#cfe2f4' , fontWeight : 700 , whiteSpace : 'nowrap' , textAlign : 'right' } }
1480+ < DialogButton
1481+ style = { { minWidth : 0 , padding : '4px 12px' , display : 'flex' , alignItems : 'center' , gap : 6 } }
1482+ onClick = { ( ) => {
1483+ const modal = showModal (
1484+ < ReportFiltersModal
1485+ filter = { filter }
1486+ setFilterMode = { setFilterMode }
1487+ gpuFilterCounts = { gpuFilterCounts }
1488+ archFilter = { archFilter }
1489+ setArchFilter = { setArchFilter }
1490+ availArchitectures = { availArchitectures }
1491+ tierFiltered = { tierFiltered }
1492+ configFilter = { configFilter }
1493+ setConfigFilterMode = { setConfigFilterMode }
1494+ configFilterCounts = { configFilterCounts }
1495+ visibleReports = { visibleReports }
1496+ onClose = { ( ) => modal . Close ( ) }
1497+ /> ,
1498+ ) ;
1499+ } }
14421500 >
1443- { t ( ) . configManager . configFilter }
1444- </ div >
1445- < Dropdown
1446- strDefaultLabel = { configFilterOptionLabel ( configFilter , configFilterCounts [ configFilter ] ) }
1447- rgOptions = { CONFIG_FILTER_ORDER . map ( ( value ) => ( {
1448- data : value ,
1449- label : configFilterOptionLabel ( value , configFilterCounts [ value ] ) ,
1450- } ) ) }
1451- selectedOption = { configFilter }
1452- onChange = { ( opt ) => setConfigFilterMode ( opt . data as ConfigFilter ) }
1453- />
1501+ { /* funnel icon */ }
1502+ < svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "currentColor" >
1503+ < path d = "M10 18h4v-2h-4v2zm-7-12v2h18V6H3zm3 7h12v-2H6v2z" />
1504+ </ svg >
1505+ { ( ( ) => {
1506+ const count = [ filter !== 'all' , archFilter !== 'all' , configFilter !== 'all' ] . filter ( Boolean ) . length ;
1507+ return count > 0 ? `Filters (${ count } )` : 'Filters' ;
1508+ } ) ( ) }
1509+ </ DialogButton >
14541510 </ Focusable >
14551511 < div style = { { flex : 1 , minHeight : 0 , overflowY : 'auto' , paddingRight : 4 } } >
14561512 < div style = { { marginBottom : 12 , color : '#9db0c4' , fontSize : 11 } } >
0 commit comments