@@ -8,23 +8,35 @@ import { Filter } from "./filter";
8
8
import Image from "next/image" ;
9
9
import { LaptopIcon } from "@radix-ui/react-icons" ;
10
10
import { FileIcon } from "@/components/ui/fileIcon" ;
11
+ import { useSearchParams } from "next/navigation" ;
12
+ import { useRouter } from "next/navigation" ;
11
13
12
14
interface FilePanelProps {
13
15
matches : SearchResultFile [ ] ;
14
16
onFilterChanged : ( filteredMatches : SearchResultFile [ ] ) => void ,
15
17
repoMetadata : Record < string , Repository > ;
16
18
}
17
19
20
+ const LANGUAGES_QUERY_PARAM = "langs" ;
21
+ const REPOS_QUERY_PARAM = "repos" ;
22
+
18
23
export const FilterPanel = ( {
19
24
matches,
20
25
onFilterChanged,
21
26
repoMetadata,
22
27
} : FilePanelProps ) => {
23
- const [ repos , setRepos ] = useState < Record < string , Entry > > ( { } ) ;
24
- const [ languages , setLanguages ] = useState < Record < string , Entry > > ( { } ) ;
25
-
26
- useEffect ( ( ) => {
27
- const _repos = aggregateMatches (
28
+ const router = useRouter ( ) ;
29
+ const searchParams = useSearchParams ( ) ;
30
+
31
+ // Helper to parse query params into sets
32
+ const getSelectedFromQuery = ( param : string ) => {
33
+ const value = searchParams . get ( param ) ;
34
+ return value ? new Set ( value . split ( ',' ) ) : new Set ( ) ;
35
+ } ;
36
+
37
+ const [ repos , setRepos ] = useState < Record < string , Entry > > ( ( ) => {
38
+ const selectedRepos = getSelectedFromQuery ( REPOS_QUERY_PARAM ) ;
39
+ return aggregateMatches (
28
40
"Repository" ,
29
41
matches ,
30
42
( key ) => {
@@ -44,17 +56,16 @@ export const FilterPanel = ({
44
56
key,
45
57
displayName : info ?. displayName ?? key ,
46
58
count : 0 ,
47
- isSelected : false ,
59
+ isSelected : selectedRepos . has ( key ) ,
48
60
Icon,
49
61
} ;
50
62
}
51
63
) ;
64
+ } ) ;
52
65
53
- setRepos ( _repos ) ;
54
- } , [ matches , repoMetadata , setRepos ] ) ;
55
-
56
- useEffect ( ( ) => {
57
- const _languages = aggregateMatches (
66
+ const [ languages , setLanguages ] = useState < Record < string , Entry > > ( ( ) => {
67
+ const selectedLanguages = getSelectedFromQuery ( LANGUAGES_QUERY_PARAM ) ;
68
+ return aggregateMatches (
58
69
"Language" ,
59
70
matches ,
60
71
( key ) => {
@@ -66,14 +77,12 @@ export const FilterPanel = ({
66
77
key,
67
78
displayName : key ,
68
79
count : 0 ,
69
- isSelected : false ,
80
+ isSelected : selectedLanguages . has ( key ) ,
70
81
Icon : Icon ,
71
82
} satisfies Entry ;
72
83
}
73
- )
74
-
75
- setLanguages ( _languages ) ;
76
- } , [ matches , setLanguages ] ) ;
84
+ ) ;
85
+ } ) ;
77
86
78
87
const onEntryClicked = useCallback ( (
79
88
key : string ,
@@ -88,28 +97,46 @@ export const FilterPanel = ({
88
97
} ) ) ;
89
98
} , [ ] ) ;
90
99
100
+ // Calls `onFilterChanged` with the filtered list of matches
101
+ // whenever the filter state changes.
91
102
useEffect ( ( ) => {
92
- const selectedRepos = new Set (
93
- Object . entries ( repos )
94
- . filter ( ( [ _ , { isSelected } ] ) => isSelected )
95
- . map ( ( [ key ] ) => key )
96
- ) ;
97
-
98
- const selectedLanguages = new Set (
99
- Object . entries ( languages )
100
- . filter ( ( [ _ , { isSelected } ] ) => isSelected )
101
- . map ( ( [ key ] ) => key )
102
- ) ;
103
+ const selectedRepos = new Set ( Object . keys ( repos ) . filter ( ( key ) => repos [ key ] . isSelected ) ) ;
104
+ const selectedLanguages = new Set ( Object . keys ( languages ) . filter ( ( key ) => languages [ key ] . isSelected ) ) ;
103
105
104
106
const filteredMatches = matches . filter ( ( match ) =>
105
107
(
106
108
( selectedRepos . size === 0 ? true : selectedRepos . has ( match . Repository ) ) &&
107
109
( selectedLanguages . size === 0 ? true : selectedLanguages . has ( match . Language ) )
108
110
)
109
111
) ;
110
-
111
112
onFilterChanged ( filteredMatches ) ;
112
- } , [ matches , repos , languages , onFilterChanged ] ) ;
113
+
114
+ } , [ matches , repos , languages , onFilterChanged , searchParams , router ] ) ;
115
+
116
+ // Updates the query params when the filter state changes
117
+ useEffect ( ( ) => {
118
+ const selectedRepos = Object . keys ( repos ) . filter ( ( key ) => repos [ key ] . isSelected ) ;
119
+ const selectedLanguages = Object . keys ( languages ) . filter ( ( key ) => languages [ key ] . isSelected ) ;
120
+
121
+ const newParams = new URLSearchParams ( searchParams . toString ( ) ) ;
122
+
123
+ if ( selectedRepos . length > 0 ) {
124
+ newParams . set ( REPOS_QUERY_PARAM , selectedRepos . join ( ',' ) ) ;
125
+ } else {
126
+ newParams . delete ( REPOS_QUERY_PARAM ) ;
127
+ }
128
+
129
+ if ( selectedLanguages . length > 0 ) {
130
+ newParams . set ( LANGUAGES_QUERY_PARAM , selectedLanguages . join ( ',' ) ) ;
131
+ } else {
132
+ newParams . delete ( LANGUAGES_QUERY_PARAM ) ;
133
+ }
134
+
135
+ // Only push if params actually changed
136
+ if ( newParams . toString ( ) !== searchParams . toString ( ) ) {
137
+ router . replace ( `?${ newParams . toString ( ) } ` , { scroll : false } ) ;
138
+ }
139
+ } , [ repos , languages , searchParams , router ] ) ;
113
140
114
141
const numRepos = Object . keys ( repos ) . length > 100 ? '100+' : Object . keys ( repos ) . length ;
115
142
const numLanguages = Object . keys ( languages ) . length > 100 ? '100+' : Object . keys ( languages ) . length ;
0 commit comments