mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 04:15:30 +00:00
fix scrolling issue
This commit is contained in:
parent
9cd32362e8
commit
9f66c458c4
3 changed files with 31 additions and 17 deletions
|
|
@ -15,6 +15,7 @@ import { useGetSelectedFromQuery } from "./useGetSelectedFromQuery";
|
||||||
interface FilePanelProps {
|
interface FilePanelProps {
|
||||||
matches: SearchResultFile[];
|
matches: SearchResultFile[];
|
||||||
repoInfo: Record<number, RepositoryInfo>;
|
repoInfo: Record<number, RepositoryInfo>;
|
||||||
|
onFilterChange?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,10 +32,12 @@ interface FilePanelProps {
|
||||||
*
|
*
|
||||||
* @param matches - Array of search result files to filter
|
* @param matches - Array of search result files to filter
|
||||||
* @param repoInfo - Information about repositories including their display names and icons
|
* @param repoInfo - Information about repositories including their display names and icons
|
||||||
|
* @param onFilterChange - Optional callback that is called whenever a filter is applied or removed
|
||||||
*/
|
*/
|
||||||
export const FilterPanel = ({
|
export const FilterPanel = ({
|
||||||
matches,
|
matches,
|
||||||
repoInfo,
|
repoInfo,
|
||||||
|
onFilterChange,
|
||||||
}: FilePanelProps) => {
|
}: FilePanelProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
@ -148,6 +151,7 @@ export const FilterPanel = ({
|
||||||
|
|
||||||
if (newParams.toString() !== searchParams.toString()) {
|
if (newParams.toString() !== searchParams.toString()) {
|
||||||
router.replace(`?${newParams.toString()}`, { scroll: false });
|
router.replace(`?${newParams.toString()}`, { scroll: false });
|
||||||
|
onFilterChange?.();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="max-h-[50%]"
|
className="max-h-[50%]"
|
||||||
|
|
@ -170,6 +174,7 @@ export const FilterPanel = ({
|
||||||
|
|
||||||
if (newParams.toString() !== searchParams.toString()) {
|
if (newParams.toString() !== searchParams.toString()) {
|
||||||
router.replace(`?${newParams.toString()}`, { scroll: false });
|
router.replace(`?${newParams.toString()}`, { scroll: false });
|
||||||
|
onFilterChange?.();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="overflow-auto"
|
className="overflow-auto"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import { useStreamedSearch } from "../useStreamedSearch";
|
||||||
import { CodePreviewPanel } from "./codePreviewPanel";
|
import { CodePreviewPanel } from "./codePreviewPanel";
|
||||||
import { FilterPanel } from "./filterPanel";
|
import { FilterPanel } from "./filterPanel";
|
||||||
import { useFilteredMatches } from "./filterPanel/useFilterMatches";
|
import { useFilteredMatches } from "./filterPanel/useFilterMatches";
|
||||||
import { SearchResultsPanel } from "./searchResultsPanel";
|
import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
|
||||||
|
|
||||||
interface SearchResultsPageProps {
|
interface SearchResultsPageProps {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
|
|
@ -198,6 +198,7 @@ const PanelGroup = ({
|
||||||
const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined);
|
const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined);
|
||||||
const filteredFileMatches = useFilteredMatches(fileMatches);
|
const filteredFileMatches = useFilteredMatches(fileMatches);
|
||||||
const filterPanelRef = useRef<ImperativePanelHandle>(null);
|
const filterPanelRef = useRef<ImperativePanelHandle>(null);
|
||||||
|
const searchResultsPanelRef = useRef<SearchResultsPanelHandle>(null);
|
||||||
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
|
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
|
||||||
|
|
||||||
const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false);
|
const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false);
|
||||||
|
|
@ -238,6 +239,9 @@ const PanelGroup = ({
|
||||||
<FilterPanel
|
<FilterPanel
|
||||||
matches={fileMatches}
|
matches={fileMatches}
|
||||||
repoInfo={repoInfo}
|
repoInfo={repoInfo}
|
||||||
|
onFilterChange={() => {
|
||||||
|
searchResultsPanelRef.current?.resetScroll();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
{isFilterPanelCollapsed && (
|
{isFilterPanelCollapsed && (
|
||||||
|
|
@ -325,6 +329,7 @@ const PanelGroup = ({
|
||||||
</div>
|
</div>
|
||||||
{filteredFileMatches.length > 0 ? (
|
{filteredFileMatches.length > 0 ? (
|
||||||
<SearchResultsPanel
|
<SearchResultsPanel
|
||||||
|
ref={searchResultsPanelRef}
|
||||||
fileMatches={filteredFileMatches}
|
fileMatches={filteredFileMatches}
|
||||||
onOpenFilePreview={(fileMatch, matchIndex) => {
|
onOpenFilePreview={(fileMatch, matchIndex) => {
|
||||||
setSelectedMatchIndex(matchIndex ?? 0);
|
setSelectedMatchIndex(matchIndex ?? 0);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
|
import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
|
||||||
import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer";
|
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, useRef, useState } from "react";
|
import { useCallback, useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react";
|
||||||
import { useDebounce, usePrevious } from "@uidotdev/usehooks";
|
import { useDebounce } from "@uidotdev/usehooks";
|
||||||
|
|
||||||
interface SearchResultsPanelProps {
|
interface SearchResultsPanelProps {
|
||||||
fileMatches: SearchResultFile[];
|
fileMatches: SearchResultFile[];
|
||||||
|
|
@ -15,6 +15,10 @@ interface SearchResultsPanelProps {
|
||||||
repoInfo: Record<number, RepositoryInfo>;
|
repoInfo: Record<number, RepositoryInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchResultsPanelHandle {
|
||||||
|
resetScroll: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const ESTIMATED_LINE_HEIGHT_PX = 20;
|
const ESTIMATED_LINE_HEIGHT_PX = 20;
|
||||||
const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10;
|
const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10;
|
||||||
const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30;
|
const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30;
|
||||||
|
|
@ -25,14 +29,14 @@ type ScrollHistoryState = {
|
||||||
showAllMatchesStates?: boolean[];
|
showAllMatchesStates?: boolean[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchResultsPanel = ({
|
export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchResultsPanelProps>(({
|
||||||
fileMatches,
|
fileMatches,
|
||||||
onOpenFilePreview,
|
onOpenFilePreview,
|
||||||
isLoadMoreButtonVisible,
|
isLoadMoreButtonVisible,
|
||||||
onLoadMoreButtonClicked,
|
onLoadMoreButtonClicked,
|
||||||
isBranchFilteringEnabled,
|
isBranchFilteringEnabled,
|
||||||
repoInfo,
|
repoInfo,
|
||||||
}: SearchResultsPanelProps) => {
|
}, ref) => {
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Restore the scroll offset, measurements cache, and other state from the history
|
// Restore the scroll offset, measurements cache, and other state from the history
|
||||||
|
|
@ -73,18 +77,16 @@ export const SearchResultsPanel = ({
|
||||||
debug: false,
|
debug: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// When the number of file matches changes, we need to reset our scroll state.
|
const resetScroll = useCallback(() => {
|
||||||
const prevFileMatches = usePrevious(fileMatches);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!prevFileMatches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevFileMatches.length !== fileMatches.length) {
|
|
||||||
setShowAllMatchesStates(Array(fileMatches.length).fill(false));
|
setShowAllMatchesStates(Array(fileMatches.length).fill(false));
|
||||||
virtualizer.scrollToIndex(0);
|
virtualizer.scrollToIndex(0);
|
||||||
}
|
}, [fileMatches.length, virtualizer]);
|
||||||
}, [fileMatches.length, prevFileMatches, virtualizer]);
|
|
||||||
|
// Expose the resetScroll function to parent components
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
resetScroll,
|
||||||
|
}), [resetScroll]);
|
||||||
|
|
||||||
|
|
||||||
// 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, 100);
|
||||||
|
|
@ -177,4 +179,6 @@ export const SearchResultsPanel = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
});
|
||||||
|
|
||||||
|
SearchResultsPanel.displayName = 'SearchResultsPanel';
|
||||||
Loading…
Reference in a new issue