overleaf-cep/services/web/frontend/js/features/template-gallery/context/template-gallery-context.tsx
2025-07-22 19:22:09 +02:00

129 lines
3.2 KiB
TypeScript

import {
createContext,
ReactNode,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Template } from '../../../../../types/template'
import { GetTemplatesResponseBody, Sort } from '../types/api'
import getMeta from '../../../utils/meta'
import useAsync from '../../../shared/hooks/use-async'
import { getTemplates } from '../util/api'
import sortTemplates from '../util/sort-templates'
import { debugConsole } from '@/utils/debugging'
export type TemplateGalleryContextValue = {
visibleTemplates: Template[]
totalTemplatesCount: number
error: Error | null
sort: Sort
setSort: React.Dispatch<React.SetStateAction<Sort>>
searchText: string
setSearchText: React.Dispatch<React.SetStateAction<string>>
}
export const TemplateGalleryContext = createContext<
TemplateGalleryContextValue | undefined
>(undefined)
type TemplateGalleryProviderProps = {
children: ReactNode
}
export function TemplateGalleryProvider({ children }: TemplateGalleryProviderProps) {
const [loadedTemplates, setLoadedTemplates] = useState<Template[]>([])
const [visibleTemplates, setVisibleTemplates] = useState<Template[]>([])
const [totalTemplatesCount, setTotalTemplatesCount] = useState<number>(0)
const [sort, setSort] = useState<Sort>({
by: 'lastUpdated',
order: 'desc',
})
const prevSortRef = useRef<Sort>(sort)
const [searchText, setSearchText] = useState('')
const {
error,
runAsync,
} = useAsync<GetTemplatesResponseBody>()
const category = getMeta('ol-templateCategory') || 'all'
useEffect(() => {
runAsync(getTemplates(sort, category))
.then(data => {
setLoadedTemplates(data.templates)
setTotalTemplatesCount(data.totalSize)
})
.catch(debugConsole.error)
.finally(() => {
})
}, [runAsync])
useEffect(() => {
let filteredTemplates = [...loadedTemplates]
if (searchText.length) {
filteredTemplates = filteredTemplates.filter(template =>
template.name.toLowerCase().includes(searchText.toLowerCase()) ||
template.description.toLowerCase().includes(searchText.toLowerCase())
)
}
if (prevSortRef.current !== sort) {
filteredTemplates = sortTemplates(filteredTemplates, sort)
const loadedTemplatesSorted = sortTemplates(loadedTemplates, sort)
setLoadedTemplates(loadedTemplatesSorted)
}
setVisibleTemplates(filteredTemplates)
}, [
loadedTemplates,
searchText,
sort,
])
useEffect(() => {
prevSortRef.current = sort
}, [sort])
const value = useMemo<TemplateGalleryContextValue>(
() => ({
error,
searchText,
setSearchText,
setSort,
sort,
totalTemplatesCount,
visibleTemplates,
}),
[
error,
searchText,
setSearchText,
setSort,
sort,
totalTemplatesCount,
visibleTemplates,
]
)
return (
<TemplateGalleryContext.Provider value={value}>
{children}
</TemplateGalleryContext.Provider>
)
}
export function useTemplateGalleryContext() {
const context = useContext(TemplateGalleryContext)
if (!context) {
throw new Error(
'TemplateGalleryContext is only available inside TemplateGalleryProvider'
)
}
return context
}