Spaces:
Running
Running
| import React, { | |
| createContext, | |
| useContext, | |
| useState, | |
| useCallback, | |
| useMemo, | |
| useEffect, | |
| } from "react"; | |
| import { normalizeTags } from "../utils/tagFilters"; | |
| import { useUrlState } from "../hooks/useUrlState"; | |
| import { | |
| CATEGORIZATION_TAGS, | |
| isUncategorized, | |
| filterLeaderboards as filterLeaderboardsUtil, | |
| generateSections, | |
| } from "../utils/filterUtils"; | |
| const LeaderboardContext = createContext(); | |
| // Helper pour déterminer si un leaderboard est non catégorisé | |
| const isUncategorizedBoard = isUncategorized; | |
| export const LeaderboardProvider = ({ children }) => { | |
| const { params, updateParams } = useUrlState(); | |
| const [leaderboards, setLeaderboards] = useState([]); | |
| const [searchQuery, setSearchQuery] = useState(params.search || ""); | |
| const [arenaOnly, setArenaOnly] = useState(params.arena === "true"); | |
| const [selectedCategories, setSelectedCategories] = useState( | |
| new Set(params.categories || []) | |
| ); | |
| const [selectedLanguage, setSelectedLanguage] = useState( | |
| params.language ? new Set(params.language.split(",")) : new Set() | |
| ); | |
| const [expandedSections, setExpandedSections] = useState( | |
| new Set(params.categories || []) | |
| ); | |
| const [isLanguageExpanded, setIsLanguageExpanded] = useState( | |
| params.languageExpanded === "true" | |
| ); | |
| useEffect(() => { | |
| updateParams({ | |
| categories: Array.from(selectedCategories), | |
| search: searchQuery, | |
| arena: arenaOnly ? "true" : null, | |
| language: | |
| selectedLanguage.size > 0 | |
| ? Array.from(selectedLanguage).join(",") | |
| : null, | |
| languageExpanded: isLanguageExpanded ? "true" : null, | |
| }); | |
| }, [ | |
| selectedCategories, | |
| searchQuery, | |
| arenaOnly, | |
| selectedLanguage, | |
| isLanguageExpanded, | |
| updateParams, | |
| ]); | |
| useEffect(() => { | |
| if (leaderboards.length > 0) { | |
| const uncategorizedLeaderboards = leaderboards.filter( | |
| (board) => | |
| board.approval_status === "approved" && isUncategorizedBoard(board) | |
| ); | |
| } | |
| }, [leaderboards]); | |
| const handleCategorySelection = useCallback((categoryId) => { | |
| setSelectedCategories((prev) => { | |
| const newCategories = new Set(prev); | |
| if (newCategories.has(categoryId)) { | |
| newCategories.delete(categoryId); | |
| setExpandedSections((prev) => { | |
| const newExpanded = new Set(prev); | |
| newExpanded.delete(categoryId); | |
| return newExpanded; | |
| }); | |
| } else { | |
| newCategories.add(categoryId); | |
| // Si on sélectionne, on déploie la section | |
| setExpandedSections((prev) => { | |
| const newExpanded = new Set(prev); | |
| newExpanded.add(categoryId); | |
| return newExpanded; | |
| }); | |
| } | |
| return newCategories; | |
| }); | |
| // On réinitialise la langue sélectionnée seulement si on désélectionne "language" | |
| if (categoryId === "language") { | |
| setSelectedLanguage(new Set()); | |
| } | |
| }, []); | |
| // Wrapper pour la sélection de langue | |
| const handleLanguageSelection = useCallback((language) => { | |
| setSelectedLanguage((prev) => { | |
| const newSet = new Set(prev); | |
| if (newSet.has(language)) { | |
| newSet.delete(language); | |
| } else { | |
| newSet.add(language); | |
| } | |
| return newSet; | |
| }); | |
| }, []); | |
| // Filter leaderboards based on search query and arena toggle, ignoring category selection | |
| const filterLeaderboardsForCount = useCallback( | |
| (boards) => { | |
| return filterLeaderboardsUtil({ | |
| boards, | |
| searchQuery, | |
| arenaOnly, | |
| }); | |
| }, | |
| [searchQuery, arenaOnly] | |
| ); | |
| // Filter leaderboards based on all criteria including categories and language selection | |
| const filterLeaderboards = useCallback( | |
| (boards) => { | |
| return filterLeaderboardsUtil({ | |
| boards, | |
| searchQuery, | |
| arenaOnly, | |
| selectedCategories, | |
| selectedLanguage, | |
| }); | |
| }, | |
| [searchQuery, arenaOnly, selectedCategories, selectedLanguage] | |
| ); | |
| // Fonction pour obtenir les leaderboards bruts d'une section | |
| const getSectionLeaderboards = useCallback((boards) => { | |
| if (!boards) return []; | |
| return [...boards]; | |
| }, []); | |
| // Filter functions for categories | |
| const filterByTag = useCallback((tag, boards) => { | |
| const searchTag = tag.toLowerCase(); | |
| return ( | |
| boards?.filter((board) => | |
| board.tags?.some((t) => t.toLowerCase() === searchTag) | |
| ) || [] | |
| ); | |
| }, []); | |
| const filterByLanguage = useCallback((boards) => { | |
| return ( | |
| boards?.filter((board) => | |
| board.tags?.some((tag) => tag.startsWith("language:")) | |
| ) || [] | |
| ); | |
| }, []); | |
| // Define sections with raw data | |
| const allSections = useMemo(() => { | |
| if (!leaderboards) return []; | |
| return generateSections(leaderboards, filterByTag, filterByLanguage); | |
| }, [leaderboards, filterByTag, filterByLanguage]); | |
| // Get sections with data | |
| const sections = useMemo(() => { | |
| return allSections.filter((section) => section.data.length > 0); | |
| }, [allSections]); | |
| // Sections triées par nombre de leaderboards (pour l'affichage sur la page d'accueil) | |
| const sectionsSortedByCount = useMemo(() => { | |
| return [...sections].sort((a, b) => { | |
| // Filtrer pour n'avoir que les leaderboards approuvés | |
| const approvedA = a.data.filter( | |
| (board) => board.approval_status === "approved" | |
| ); | |
| const approvedB = b.data.filter( | |
| (board) => board.approval_status === "approved" | |
| ); | |
| return approvedB.length - approvedA.length; // Tri décroissant | |
| }); | |
| }, [sections]); | |
| // Calculate total number of unique leaderboards (excluding duplicates) | |
| const totalLeaderboards = useMemo(() => { | |
| const uniqueIds = new Set( | |
| leaderboards | |
| .filter((board) => board.approval_status === "approved") | |
| .map((board) => board.id) | |
| ); | |
| return uniqueIds.size; | |
| }, [leaderboards]); | |
| // Get filtered count | |
| const filteredCount = useMemo(() => { | |
| return filterLeaderboards(leaderboards).length; | |
| }, [filterLeaderboards, leaderboards]); | |
| // Function to get highlighted parts of text | |
| const getHighlightedText = useCallback((text, searchTerm) => { | |
| if (!searchTerm || !text) return { text, shouldHighlight: false }; | |
| const query = searchTerm.toLowerCase(); | |
| const searchableTagPrefixes = [ | |
| "domain:", | |
| "language:", | |
| "judge:", | |
| "test:", | |
| "modality:", | |
| "submission:", | |
| "eval:", | |
| ]; | |
| // Si c'est une recherche par tag, on ne highlight rien | |
| if (searchableTagPrefixes.some((prefix) => query.startsWith(prefix))) { | |
| return { text, shouldHighlight: false }; | |
| } | |
| // Sinon on highlight les parties qui matchent | |
| const index = text.toLowerCase().indexOf(query); | |
| if (index === -1) return { text, shouldHighlight: false }; | |
| return { | |
| text, | |
| shouldHighlight: true, | |
| highlightStart: index, | |
| highlightEnd: index + query.length, | |
| }; | |
| }, []); | |
| // Wrapper pour setLeaderboards qui normalise les tags | |
| const setNormalizedLeaderboards = useCallback((boards) => { | |
| const normalizedBoards = boards.map((board) => ({ | |
| ...board, | |
| tags: normalizeTags(board.tags), | |
| })); | |
| setLeaderboards(normalizedBoards); | |
| }, []); | |
| const resetState = useCallback(() => { | |
| setSearchQuery(""); | |
| setArenaOnly(false); | |
| setSelectedCategories(new Set()); | |
| setSelectedLanguage(new Set()); | |
| setExpandedSections(new Set()); | |
| setIsLanguageExpanded(false); | |
| }, []); | |
| const value = { | |
| leaderboards, | |
| setLeaderboards: setNormalizedLeaderboards, | |
| searchQuery, | |
| setSearchQuery, | |
| arenaOnly, | |
| setArenaOnly, | |
| totalLeaderboards, | |
| filteredCount, | |
| filterLeaderboards, | |
| filterLeaderboardsForCount, | |
| sections, | |
| sectionsSortedByCount, | |
| allSections, | |
| getHighlightedText, | |
| selectedCategories, | |
| setSelectedCategories: handleCategorySelection, | |
| selectedLanguage, | |
| setSelectedLanguage: handleLanguageSelection, | |
| expandedSections, | |
| setExpandedSections, | |
| resetState, | |
| isLanguageExpanded, | |
| setIsLanguageExpanded, | |
| }; | |
| return ( | |
| <LeaderboardContext.Provider value={value}> | |
| {children} | |
| </LeaderboardContext.Provider> | |
| ); | |
| }; | |
| export const useLeaderboard = () => { | |
| const context = useContext(LeaderboardContext); | |
| if (!context) { | |
| throw new Error("useLeaderboard must be used within a LeaderboardProvider"); | |
| } | |
| return context; | |
| }; | |