@@ -8,23 +8,35 @@ import { Filter } from "./filter";
88import Image from "next/image" ;
99import { LaptopIcon } from "@radix-ui/react-icons" ;
1010import { FileIcon } from "@/components/ui/fileIcon" ;
11+ import { useSearchParams } from "next/navigation" ;
12+ import { useRouter } from "next/navigation" ;
1113
1214interface FilePanelProps {
1315 matches : SearchResultFile [ ] ;
1416 onFilterChanged : ( filteredMatches : SearchResultFile [ ] ) => void ,
1517 repoMetadata : Record < string , Repository > ;
1618}
1719
20+ const LANGUAGES_QUERY_PARAM = "langs" ;
21+ const REPOS_QUERY_PARAM = "repos" ;
22+
1823export const FilterPanel = ( {
1924 matches,
2025 onFilterChanged,
2126 repoMetadata,
2227} : 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 (
2840 "Repository" ,
2941 matches ,
3042 ( key ) => {
@@ -44,17 +56,16 @@ export const FilterPanel = ({
4456 key,
4557 displayName : info ?. displayName ?? key ,
4658 count : 0 ,
47- isSelected : false ,
59+ isSelected : selectedRepos . has ( key ) ,
4860 Icon,
4961 } ;
5062 }
5163 ) ;
64+ } ) ;
5265
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 (
5869 "Language" ,
5970 matches ,
6071 ( key ) => {
@@ -66,14 +77,12 @@ export const FilterPanel = ({
6677 key,
6778 displayName : key ,
6879 count : 0 ,
69- isSelected : false ,
80+ isSelected : selectedLanguages . has ( key ) ,
7081 Icon : Icon ,
7182 } satisfies Entry ;
7283 }
73- )
74-
75- setLanguages ( _languages ) ;
76- } , [ matches , setLanguages ] ) ;
84+ ) ;
85+ } ) ;
7786
7887 const onEntryClicked = useCallback ( (
7988 key : string ,
@@ -88,28 +97,46 @@ export const FilterPanel = ({
8897 } ) ) ;
8998 } , [ ] ) ;
9099
100+ // Calls `onFilterChanged` with the filtered list of matches
101+ // whenever the filter state changes.
91102 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 ) ) ;
103105
104106 const filteredMatches = matches . filter ( ( match ) =>
105107 (
106108 ( selectedRepos . size === 0 ? true : selectedRepos . has ( match . Repository ) ) &&
107109 ( selectedLanguages . size === 0 ? true : selectedLanguages . has ( match . Language ) )
108110 )
109111 ) ;
110-
111112 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 ] ) ;
113140
114141 const numRepos = Object . keys ( repos ) . length > 100 ? '100+' : Object . keys ( repos ) . length ;
115142 const numLanguages = Object . keys ( languages ) . length > 100 ? '100+' : Object . keys ( languages ) . length ;
0 commit comments