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" | |
); | |
// Mettre à jour l'URL quand les filtres changent | |
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, | |
]); | |
// Logger les leaderboards non catégorisés | |
useEffect(() => { | |
if (leaderboards.length > 0) { | |
const uncategorizedLeaderboards = leaderboards.filter( | |
(board) => | |
board.approval_status === "approved" && isUncategorizedBoard(board) | |
); | |
if (uncategorizedLeaderboards.length > 0) { | |
console.log("=== LEADERBOARDS NON CATÉGORISÉS ==="); | |
console.log( | |
`Total: ${uncategorizedLeaderboards.length} leaderboards non catégorisés` | |
); | |
console.table( | |
uncategorizedLeaderboards.map((board) => ({ | |
id: board.id, | |
uid: board.uid, | |
title: board.card_data?.title || "Sans titre", | |
tags: (board.tags || []).join(", "), | |
url: board.card_data?.url || "", | |
})) | |
); | |
} | |
} | |
}, [leaderboards]); | |
// Wrapper pour setSelectedCategories qui gère aussi l'expansion des sections | |
const handleCategorySelection = useCallback((categoryId) => { | |
setSelectedCategories((prev) => { | |
const newCategories = new Set(prev); | |
if (newCategories.has(categoryId)) { | |
newCategories.delete(categoryId); | |
// Si on désélectionne, on replie la section | |
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; | |
}; | |