fix filter

This commit is contained in:
bkellam 2025-11-18 11:58:36 -08:00
parent 09581f3cc2
commit b09def9ddd
2 changed files with 43 additions and 35 deletions

View file

@ -1,10 +1,11 @@
'use client'; 'use client';
import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer";
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"; import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual";
import { useCallback, useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react";
import { useDebounce } from "@uidotdev/usehooks"; import { useDebounce } from "@uidotdev/usehooks";
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import { useMap } from "usehooks-ts";
import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer";
interface SearchResultsPanelProps { interface SearchResultsPanelProps {
fileMatches: SearchResultFile[]; fileMatches: SearchResultFile[];
@ -26,7 +27,15 @@ const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30;
type ScrollHistoryState = { type ScrollHistoryState = {
scrollOffset?: number; scrollOffset?: number;
measurementsCache?: VirtualItem[]; measurementsCache?: VirtualItem[];
showAllMatchesStates?: boolean[]; showAllMatchesMap?: [string, boolean][];
}
/**
* Unique key for a given file match. Used to store the "show all matches" state for a
* given file match.
*/
const getFileMatchKey = (fileMatch: SearchResultFile) => {
return `${fileMatch.repository}-${fileMatch.fileName.text}`;
} }
export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchResultsPanelProps>(({ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchResultsPanelProps>(({
@ -46,17 +55,17 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
const { const {
scrollOffset: restoreOffset, scrollOffset: restoreOffset,
measurementsCache: restoreMeasurementsCache, measurementsCache: restoreMeasurementsCache,
showAllMatchesStates: restoreShowAllMatchesStates, showAllMatchesMap: restoreShowAllMatchesStates,
} = history.state as ScrollHistoryState; } = history.state as ScrollHistoryState;
const [showAllMatchesStates, setShowAllMatchesStates] = useState(restoreShowAllMatchesStates || Array(fileMatches.length).fill(false)); const [showAllMatchesMap, showAllMatchesActions] = useMap<string, boolean>(restoreShowAllMatchesStates || []);
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count: fileMatches.length, count: fileMatches.length,
getScrollElement: () => parentRef.current, getScrollElement: () => parentRef.current,
estimateSize: (index) => { estimateSize: (index) => {
const fileMatch = fileMatches[index]; const fileMatch = fileMatches[index];
const showAllMatches = showAllMatchesStates[index]; const showAllMatches = showAllMatchesMap.get(getFileMatchKey(fileMatch));
// Quick guesstimation ;) This needs to be quick since the virtualizer will // Quick guesstimation ;) This needs to be quick since the virtualizer will
// run this upfront for all items in the list. // run this upfront for all items in the list.
@ -78,7 +87,6 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
}); });
const resetScroll = useCallback(() => { const resetScroll = useCallback(() => {
setShowAllMatchesStates(Array(fileMatches.length).fill(false));
virtualizer.scrollToIndex(0); virtualizer.scrollToIndex(0);
}, [fileMatches.length, virtualizer]); }, [fileMatches.length, virtualizer]);
@ -89,24 +97,22 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
// Save the scroll state to the history stack. // Save the scroll state to the history stack.
const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 100); const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 500);
useEffect(() => { useEffect(() => {
history.replaceState( history.replaceState(
{ {
scrollOffset: debouncedScrollOffset ?? undefined, scrollOffset: debouncedScrollOffset ?? undefined,
measurementsCache: virtualizer.measurementsCache, measurementsCache: virtualizer.measurementsCache,
showAllMatchesStates, showAllMatchesMap: Array.from(showAllMatchesMap.entries()),
} satisfies ScrollHistoryState, } satisfies ScrollHistoryState,
'', '',
window.location.href window.location.href
); );
}, [debouncedScrollOffset, virtualizer.measurementsCache, showAllMatchesStates]); }, [debouncedScrollOffset, virtualizer.measurementsCache, showAllMatchesMap]);
const onShowAllMatchesButtonClicked = useCallback((index: number) => { const onShowAllMatchesButtonClicked = useCallback((fileMatchKey: string, index: number) => {
const states = [...showAllMatchesStates]; const wasShown = showAllMatchesMap.get(fileMatchKey) ?? false;
const wasShown = states[index]; showAllMatchesActions.set(fileMatchKey, !wasShown);
states[index] = !wasShown;
setShowAllMatchesStates(states);
// When collapsing, scroll to the top of the file match container. This ensures // When collapsing, scroll to the top of the file match container. This ensures
// that the focused "show fewer matches" button is visible. // that the focused "show fewer matches" button is visible.
@ -115,7 +121,7 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
align: 'start' align: 'start'
}); });
} }
}, [showAllMatchesStates, virtualizer]); }, [showAllMatchesMap, virtualizer]);
return ( return (
@ -155,9 +161,9 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
onOpenFilePreview={(matchIndex) => { onOpenFilePreview={(matchIndex) => {
onOpenFilePreview(file, matchIndex); onOpenFilePreview(file, matchIndex);
}} }}
showAllMatches={showAllMatchesStates[virtualRow.index]} showAllMatches={showAllMatchesMap.get(getFileMatchKey(file)) ?? false}
onShowAllMatchesButtonClicked={() => { onShowAllMatchesButtonClicked={() => {
onShowAllMatchesButtonClicked(virtualRow.index); onShowAllMatchesButtonClicked(getFileMatchKey(file), virtualRow.index);
}} }}
isBranchFilteringEnabled={isBranchFilteringEnabled} isBranchFilteringEnabled={isBranchFilteringEnabled}
repoInfo={repoInfo} repoInfo={repoInfo}
@ -181,4 +187,4 @@ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchRes
) )
}); });
SearchResultsPanel.displayName = 'SearchResultsPanel'; SearchResultsPanel.displayName = 'SearchResultsPanel';

View file

@ -1,24 +1,25 @@
import { Tree, SyntaxNode } from "@sourcebot/query-language";
import { Q } from '@/proto/zoekt/webserver/v1/Q'; import { Q } from '@/proto/zoekt/webserver/v1/Q';
import { import {
Program,
AndExpr, AndExpr,
OrExpr, ArchivedExpr,
NegateExpr,
ParenExpr,
PrefixExpr,
Term,
FileExpr,
RepoExpr,
RevisionExpr,
ContentExpr, ContentExpr,
ContextExpr, ContextExpr,
LangExpr, FileExpr,
SymExpr,
ArchivedExpr,
ForkExpr, ForkExpr,
VisibilityExpr, LangExpr,
NegateExpr,
OrExpr,
ParenExpr,
PrefixExpr,
Program,
RepoExpr,
RepoSetExpr, RepoSetExpr,
RevisionExpr,
SymExpr,
SyntaxNode,
Term,
Tree,
VisibilityExpr,
} from '@sourcebot/query-language'; } from '@sourcebot/query-language';
type ArchivedValue = 'yes' | 'no' | 'only'; type ArchivedValue = 'yes' | 'no' | 'only';
@ -227,12 +228,13 @@ export const transformToZoektQuery = ({
const flags: ('FLAG_ONLY_PUBLIC' | 'FLAG_ONLY_PRIVATE')[] = []; const flags: ('FLAG_ONLY_PUBLIC' | 'FLAG_ONLY_PRIVATE')[] = [];
if (rawValue === 'public') { if (rawValue === 'any') {
// 'any' means no filter
} else if (rawValue === 'public') {
flags.push('FLAG_ONLY_PUBLIC'); flags.push('FLAG_ONLY_PUBLIC');
} else if (rawValue === 'private') { } else if (rawValue === 'private') {
flags.push('FLAG_ONLY_PRIVATE'); flags.push('FLAG_ONLY_PRIVATE');
} }
// 'any' means no filter
return { return {
raw_config: { raw_config: {