From b7ad4558ef996d82aaa50a70fba1cafaad74de09 Mon Sep 17 00:00:00 2001 From: rootwhois Date: Tue, 16 Sep 2025 21:40:42 +0800 Subject: [PATCH] feat(repositories): switch to infinite scroll with consistent card widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace pagination with on-demand infinite scrolling (load 50 per batch) - Use IntersectionObserver and a bottom sentinel to trigger loading - Update stats to show current range “X–Y / N repositories” - Switch from CSS columns to fixed grid to ensure consistent card widths - Remove pagination state and controls - Clean up unused variables and resolve lint warnings UX: smoother scrolling, stable card widths, more natural loading behavior. --- src/components/RepositoryList.tsx | 62 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/src/components/RepositoryList.tsx b/src/components/RepositoryList.tsx index c02027c5..0c47b46d 100644 --- a/src/components/RepositoryList.tsx +++ b/src/components/RepositoryList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Bot, ChevronDown, Pause, Play } from 'lucide-react'; import { RepositoryCard } from './RepositoryCard'; @@ -32,7 +32,6 @@ export const RepositoryList: React.FC = ({ const [showAISummary, setShowAISummary] = useState(true); const [showDropdown, setShowDropdown] = useState(false); const [isPaused, setIsPaused] = useState(false); - const [searchTime, setSearchTime] = useState(undefined); // 使用 useRef 来管理停止状态,确保在异步操作中能正确访问最新值 const shouldStopRef = useRef(false); @@ -76,6 +75,43 @@ export const RepositoryList: React.FC = ({ ); }); + // Infinite scroll (瀑布流按需加载) + const LOAD_BATCH = 50; + const [visibleCount, setVisibleCount] = useState(LOAD_BATCH); + const sentinelRef = useRef(null); + + const startIndex = filteredRepositories.length === 0 ? 0 : 1; + const endIndex = Math.min(visibleCount, filteredRepositories.length); + const visibleRepositories = filteredRepositories.slice(0, visibleCount); + + // Reset visible count when filters or data change + useEffect(() => { + setVisibleCount(LOAD_BATCH); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedCategory, repositories, filteredRepositories.length]); + + // IntersectionObserver to load more on demand + useEffect(() => { + const node = sentinelRef.current; + if (!node) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting) { + setVisibleCount((count) => { + if (count >= filteredRepositories.length) return count; + return Math.min(count + LOAD_BATCH, filteredRepositories.length); + }); + } + }, + { root: null, rootMargin: '200px', threshold: 0 } + ); + + observer.observe(node); + return () => observer.disconnect(); + }, [filteredRepositories.length]); + const handleAIAnalyze = async (analyzeUnanalyzedOnly: boolean = false, analyzeFailedOnly: boolean = false) => { if (!githubToken) { alert(language === 'zh' ? 'GitHub token 未找到,请重新登录。' : 'GitHub token not found. Please login again.'); @@ -136,7 +172,7 @@ export const RepositoryList: React.FC = ({ const concurrency = activeConfig.concurrency || 1; // 并发分析函数 - const analyzeRepository = async (repo: Repository, index: number) => { + const analyzeRepository = async (repo: Repository) => { // 检查是否需要停止 if (shouldStopRef.current) { return false; @@ -201,9 +237,7 @@ export const RepositoryList: React.FC = ({ } const batch = targetRepos.slice(i, i + concurrency); - const promises = batch.map((repo, batchIndex) => - analyzeRepository(repo, i + batchIndex) - ); + const promises = batch.map((repo) => analyzeRepository(repo)); await Promise.all(promises); @@ -434,7 +468,10 @@ export const RepositoryList: React.FC = ({
- {t(`显示 ${filteredRepositories.length} 个仓库`, `Showing ${filteredRepositories.length} repositories`)} + {t( + `第 ${startIndex}-${endIndex} / 共 ${filteredRepositories.length} 个仓库`, + `Showing ${startIndex}-${endIndex} of ${filteredRepositories.length} repositories` + )} {repositories.length !== filteredRepositories.length && ( {t(`(从 ${repositories.length} 个中筛选)`, `(filtered from ${repositories.length})`)} @@ -462,17 +499,22 @@ export const RepositoryList: React.FC = ({
- {/* Repository Grid */} + {/* Repository Grid with consistent card widths */}
- {filteredRepositories.map(repo => ( + {visibleRepositories.map(repo => ( ))}
+ + {/* Sentinel for on-demand loading */} + {visibleCount < filteredRepositories.length && ( +
+ )}
); }; \ No newline at end of file