Skip to content

Commit 9f5d27b

Browse files
authored
Merge pull request #2013 from oasisprotocol/csillag/flex-filter-rofl-app-list-by-name
Add support for filtering Rofl app list by name (flex search version)
2 parents 4456efa + 4e74873 commit 9f5d27b

File tree

8 files changed

+252
-55
lines changed

8 files changed

+252
-55
lines changed

.changelog/2013.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for filtering Rofl app list by name

src/app/components/Rofl/RoflAppsList.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,23 @@ import { TableHeaderAge } from '../TableHeaderAge'
88
import { TableCellAge } from '../TableCellAge'
99
import { RoflAppStatusBadge } from './RoflAppStatusBadge'
1010
import { RoflAppLink } from './RoflAppLink'
11+
import { HighlightPattern } from '../HighlightedText'
1112

1213
type RoflAppsListProps = {
1314
apps?: RoflApp[]
1415
isLoading: boolean
1516
limit: number
1617
pagination: TablePaginationProps | false
18+
highlightPattern?: HighlightPattern
1719
}
1820

19-
export const RoflAppsList: FC<RoflAppsListProps> = ({ isLoading, limit, pagination, apps }) => {
21+
export const RoflAppsList: FC<RoflAppsListProps> = ({
22+
isLoading,
23+
limit,
24+
pagination,
25+
apps,
26+
highlightPattern,
27+
}) => {
2028
const { t } = useTranslation()
2129
const { isTablet } = useScreenSize()
2230

@@ -50,7 +58,12 @@ export const RoflAppsList: FC<RoflAppsListProps> = ({ isLoading, limit, paginati
5058
},
5159
{
5260
content: app?.metadata['net.oasis.rofl.name'] ? (
53-
<RoflAppLink id={app.id} name={app?.metadata['net.oasis.rofl.name']} network={app.network} />
61+
<RoflAppLink
62+
id={app.id}
63+
name={app?.metadata['net.oasis.rofl.name']}
64+
network={app.network}
65+
highlightPattern={highlightPattern}
66+
/>
5467
) : (
5568
t('rofl.nameNotProvided')
5669
),

src/app/components/Search/TableSearchBar.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useEffect, useState } from 'react'
1+
import { FC, KeyboardEventHandler, useCallback, useEffect, useState } from 'react'
22
import TextField from '@mui/material/TextField'
33
import SearchIcon from '@mui/icons-material/Search'
44
import HighlightOffIcon from '@mui/icons-material/HighlightOff'
@@ -14,6 +14,8 @@ import Button from '@mui/material/Button'
1414
import { CardEmptyState } from '../CardEmptyState'
1515
import { inputBaseClasses } from '@mui/material/InputBase'
1616

17+
type SearchBarSize = 'small' | 'medium' | 'large'
18+
1719
export interface TableSearchBarProps {
1820
placeholder: string
1921
warning?: string
@@ -31,6 +33,25 @@ export interface TableSearchBarProps {
3133
width?: string | number
3234

3335
onChange: (value: string) => void
36+
onEnter?: () => void
37+
size?: SearchBarSize
38+
autoFocus?: boolean
39+
}
40+
41+
type SizingInfo = {
42+
font: number | string
43+
}
44+
45+
const sizeMapping: Record<SearchBarSize, SizingInfo> = {
46+
small: {
47+
font: '1em',
48+
},
49+
medium: {
50+
font: '1.25em',
51+
},
52+
large: {
53+
font: '1.5em',
54+
},
3455
}
3556

3657
export const TableSearchBar: FC<TableSearchBarProps> = ({
@@ -39,6 +60,9 @@ export const TableSearchBar: FC<TableSearchBarProps> = ({
3960
placeholder,
4061
warning,
4162
fullWidth,
63+
size = 'medium',
64+
autoFocus,
65+
onEnter,
4266
width = 250,
4367
}) => {
4468
const { isTablet } = useScreenSize()
@@ -56,6 +80,15 @@ export const TableSearchBar: FC<TableSearchBarProps> = ({
5680
}
5781
}, [warning])
5882

83+
const handleKeyPress: KeyboardEventHandler<HTMLInputElement> = useCallback(
84+
event => {
85+
if (event.key === 'Enter') {
86+
if (onEnter) onEnter()
87+
}
88+
},
89+
[onEnter],
90+
)
91+
5992
const startAdornment = (
6093
<InputAdornment
6194
position="start"
@@ -123,13 +156,16 @@ export const TableSearchBar: FC<TableSearchBarProps> = ({
123156
variant={'outlined'}
124157
value={value}
125158
onChange={e => onChange(e.target.value)}
159+
onKeyDown={handleKeyPress}
126160
InputProps={{
127161
inputProps: {
128162
sx: {
129163
p: 0,
130164
width: fullWidth ? '100%' : width,
131165
margin: 2,
166+
fontSize: sizeMapping[size].font,
132167
},
168+
autoFocus,
133169
},
134170
startAdornment,
135171
endAdornment,

src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export const ProposalVotesCard: FC = () => {
140140
placeholder={t('networkProposal.searchForVoter')}
141141
warning={nameError}
142142
fullWidth={isTablet}
143+
size={'small'}
143144
/>
144145
</Box>
145146
}

src/app/pages/RoflAppDetailsPage/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ export const RoflAppDetailsViewSearchResult: FC<{
229229
export const RoflAppDetailsVerticalListView: FC<{
230230
isLoading?: boolean
231231
app: RoflApp | undefined
232-
}> = ({ app, isLoading }) => {
232+
highlightPattern?: HighlightPattern
233+
}> = ({ app, isLoading, highlightPattern }) => {
233234
const { t } = useTranslation()
234235
const { isMobile } = useScreenSize()
235236

@@ -238,7 +239,7 @@ export const RoflAppDetailsVerticalListView: FC<{
238239

239240
return (
240241
<StyledDescriptionList titleWidth={isMobile ? '100px' : '200px'} standalone>
241-
<NameRow name={app.metadata['net.oasis.rofl.name']} />
242+
<NameRow name={app.metadata['net.oasis.rofl.name']} highlightPattern={highlightPattern} />
242243
<StatusBadgeRow hasActiveInstances={!!app.num_active_instances} removed={app.removed} />
243244
<DetailsRow title={t('rofl.appId')}>
244245
<RoflAppLink id={app.id} network={app.network} withSourceIndicator={false} />

src/app/pages/RoflAppsPage/hook.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Runtime, useGetRuntimeRoflApps } from 'oasis-nexus/api'
2+
import { TableLayout } from '../../components/TableLayoutButton'
3+
import { useCallback, useEffect, useState } from 'react'
4+
import { useScreenSize } from '../../hooks/useScreensize'
5+
import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination'
6+
import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../../config'
7+
import { TablePaginationProps } from '../../components/Table/TablePagination'
8+
import { Network } from '../../../types/network'
9+
import { useTranslation } from 'react-i18next'
10+
import { useTypedSearchParam } from '../../hooks/useTypedSearchParam'
11+
import { textSearch } from '../../components/Search/search-utils'
12+
import { getHighlightPattern } from '../../components/Search/search-utils'
13+
import { useNavigate, useSearchParams } from 'react-router-dom'
14+
import { encodeURIComponentPretty, RouteUtils } from '../../utils/route-utils'
15+
16+
const limit = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE
17+
18+
export const useTableViewMode = () => {
19+
const { isMobile } = useScreenSize()
20+
const [tableView, setTableView] = useState<TableLayout>(TableLayout.Horizontal)
21+
22+
useEffect(() => {
23+
if (!isMobile) {
24+
setTableView(TableLayout.Horizontal)
25+
}
26+
}, [isMobile, setTableView])
27+
28+
return { tableView, setTableView }
29+
}
30+
31+
const ROFL_SEARCH_ARG_NAME = 'name'
32+
33+
export const useROFLAppFiltering = () => {
34+
const setSearchParams = useSearchParams()[1]
35+
const { t } = useTranslation()
36+
const [wantedNameInput, setWantedNameInput] = useTypedSearchParam(ROFL_SEARCH_ARG_NAME, '', {
37+
deleteParams: ['page'],
38+
})
39+
const search = textSearch.roflAppName(wantedNameInput, t)
40+
const { result: wantedNamePattern, warning: nameError } = search
41+
const highlightPattern = getHighlightPattern(search)
42+
const hasFilters = !!wantedNamePattern.length
43+
const clearFilters = () => {
44+
setSearchParams(searchParams => {
45+
searchParams.delete('name')
46+
searchParams.delete('page')
47+
return searchParams
48+
})
49+
}
50+
return {
51+
wantedNameInput,
52+
setWantedNameInput,
53+
nameError,
54+
wantedNamePattern,
55+
highlightPattern,
56+
hasFilters,
57+
clearFilters,
58+
}
59+
}
60+
61+
export const useRoflApps = (network: Network, layer: Runtime) => {
62+
const { tableView } = useTableViewMode()
63+
const navigate = useNavigate()
64+
const pagination = useSearchParamsPagination('page')
65+
const { wantedNameInput, wantedNamePattern, hasFilters } = useROFLAppFiltering()
66+
const offset = (pagination.selectedPage - 1) * limit
67+
const roflAppsQuery = useGetRuntimeRoflApps(network, layer, {
68+
name: wantedNamePattern,
69+
limit: tableView === TableLayout.Vertical ? offset + limit : limit,
70+
offset: tableView === TableLayout.Vertical ? 0 : offset,
71+
})
72+
const { isLoading, isFetched, data } = roflAppsQuery
73+
const roflApps = data?.data.rofl_apps
74+
75+
const tablePagination: TablePaginationProps = {
76+
selectedPage: pagination.selectedPage,
77+
linkToPage: pagination.linkToPage,
78+
totalCount: data?.data.total_count,
79+
isTotalCountClipped: data?.data.is_total_count_clipped,
80+
rowsPerPage: limit,
81+
}
82+
83+
const hasData = !!roflApps?.length
84+
const isOnFirstPage = pagination.selectedPage === 1
85+
86+
const hasNoResultsOnSelectedPage = isFetched && !isOnFirstPage && !hasData
87+
const hasNoResultsBecauseOfFilters = !isLoading && !hasData && isOnFirstPage && hasFilters
88+
89+
const jumpToSingleResult = useCallback(() => {
90+
// If we only have a single result
91+
if (hasData && isOnFirstPage && roflApps?.length === 1) {
92+
// Then let's jump to it
93+
const path = `${RouteUtils.getRoflAppRoute(network, roflApps![0].id)}?q=${encodeURIComponentPretty(wantedNameInput)}`
94+
navigate(path)
95+
}
96+
}, [hasData, isOnFirstPage, roflApps, navigate, network, wantedNameInput])
97+
98+
return {
99+
isLoading,
100+
limit,
101+
roflApps,
102+
pagination,
103+
tablePagination,
104+
hasNoResultsOnSelectedPage,
105+
hasNoResultsBecauseOfFilters,
106+
jumpToSingleResult,
107+
}
108+
}

0 commit comments

Comments
 (0)