190 lines
5.7 KiB
TypeScript
190 lines
5.7 KiB
TypeScript
import {
|
|
Accessor,
|
|
JSX,
|
|
createContext,
|
|
createEffect,
|
|
createMemo,
|
|
createSignal,
|
|
on,
|
|
useContext
|
|
} from 'solid-js'
|
|
import { getAuthor, loadAuthors, loadAuthorsAll } from '~/graphql/api/public'
|
|
import {
|
|
Author,
|
|
Maybe,
|
|
QueryGet_AuthorArgs,
|
|
QueryLoad_Authors_ByArgs,
|
|
Shout,
|
|
Topic
|
|
} from '~/graphql/schema/core.gen'
|
|
import { byStat } from '~/lib/sortby'
|
|
import { useFeed } from './feed'
|
|
|
|
const TOP_AUTHORS_COUNT = 5
|
|
|
|
type FilterFunction<Author> = (a: Author) => boolean
|
|
export type SortFunction<Author> = (a: Author, b: Author) => number
|
|
|
|
// Универсальная функция фильтрации и сортировки
|
|
function filterAndSort<Author>(
|
|
items: Author[],
|
|
sortFunction: SortFunction<Author>,
|
|
filters: FilterFunction<Author>[] = []
|
|
): Author[] {
|
|
return items.filter((a: Author) => filters.every((filter) => filter(a))).sort(sortFunction)
|
|
}
|
|
|
|
type AuthorsContextType = {
|
|
authorsEntities: Accessor<Record<string, Author>>
|
|
authorsSorted: Accessor<Author[]>
|
|
addAuthors: (authors: Author[]) => void
|
|
addAuthor: (author: Author) => void
|
|
loadAuthor: (args: QueryGet_AuthorArgs) => Promise<void>
|
|
loadAuthors: (args: QueryLoad_Authors_ByArgs) => Promise<void>
|
|
topAuthors: Accessor<Author[]>
|
|
authorsByTopic: Accessor<{ [topicSlug: string]: Author[] }>
|
|
setAuthorsSort: (stat: string) => void
|
|
loadAllAuthors: () => Promise<Author[]>
|
|
}
|
|
|
|
const AuthorsContext = createContext<AuthorsContextType>({} as AuthorsContextType)
|
|
|
|
export const useAuthors = () => useContext(AuthorsContext)
|
|
|
|
export const AuthorsProvider = (props: { children: JSX.Element }) => {
|
|
const [authorsEntities, setAuthors] = createSignal<Record<string, Author>>({})
|
|
const [authorsSorted, setAuthorsSorted] = createSignal<Author[]>([])
|
|
const [sortBy, setSortBy] = createSignal<SortFunction<Author>>()
|
|
const { feedByAuthor } = useFeed()
|
|
const setAuthorsSort = (stat: string) => setSortBy((_) => byStat(stat) as SortFunction<Author>)
|
|
|
|
// Эффект для отслеживания изменений сигнала sortBy и обновления authorsSorted
|
|
createEffect(
|
|
on(
|
|
[sortBy, authorsEntities],
|
|
([sortfn, authorsdict]) => {
|
|
if (sortfn) {
|
|
setAuthorsSorted?.([...filterAndSort(Object.values(authorsdict), sortfn)])
|
|
}
|
|
},
|
|
{ defer: true }
|
|
)
|
|
)
|
|
|
|
const addAuthors = (newAuthors: Author[]) => {
|
|
console.debug('[context.authors] storing new authors:', newAuthors)
|
|
setAuthors((prevAuthors) => {
|
|
const updatedAuthors = { ...prevAuthors }
|
|
newAuthors.forEach((author) => {
|
|
updatedAuthors[author.slug] = author
|
|
})
|
|
return updatedAuthors
|
|
})
|
|
}
|
|
|
|
const addAuthor = (newAuthor: Author) => {
|
|
setAuthors((prevAuthors) => {
|
|
const updatedAuthors = { ...prevAuthors }
|
|
updatedAuthors[newAuthor.slug] = newAuthor
|
|
return updatedAuthors
|
|
})
|
|
}
|
|
|
|
const loadAuthor = async (opts: QueryGet_AuthorArgs): Promise<void> => {
|
|
try {
|
|
const fetcher = await getAuthor(opts)
|
|
const author = await fetcher()
|
|
if (author) addAuthor(author as Author)
|
|
} catch (error) {
|
|
console.error('Error loading author:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const topAuthors = createMemo(() => {
|
|
const articlesByAuthorMap = feedByAuthor?.() || {}
|
|
|
|
// Получаем всех авторов
|
|
const authors = Object.keys(articlesByAuthorMap).map((authorSlug) => ({
|
|
slug: authorSlug,
|
|
rating: articlesByAuthorMap[authorSlug].reduce(
|
|
(acc: number, article: Shout) => acc + (article.stat?.rating || 0),
|
|
0
|
|
)
|
|
}))
|
|
|
|
// Определяем функцию сортировки по рейтингу
|
|
const sortByRating: SortFunction<{ slug: string; rating: number }> = (a, b) => b.rating - a.rating
|
|
|
|
// Фильтруем и сортируем авторов
|
|
const sortedTopAuthors = filterAndSort(authors, sortByRating)
|
|
.slice(0, TOP_AUTHORS_COUNT)
|
|
.map((author) => authorsEntities()[author.slug])
|
|
.filter(Boolean)
|
|
|
|
return sortedTopAuthors
|
|
})
|
|
|
|
const loadAuthorsPage = async (args: QueryLoad_Authors_ByArgs): Promise<void> => {
|
|
try {
|
|
const fetcher = await loadAuthors(args)
|
|
const data = await fetcher()
|
|
if (data) addAuthors(data as Author[])
|
|
} catch (error) {
|
|
console.error('Error loading authors:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const authorsByTopic = createMemo(() => {
|
|
const articlesByAuthorMap = feedByAuthor?.() || {}
|
|
const result: { [topicSlug: string]: Author[] } = {}
|
|
|
|
Object.values(articlesByAuthorMap).forEach((articles) => {
|
|
articles.forEach((article) => {
|
|
const { authors, topics } = article
|
|
if (topics) {
|
|
topics.forEach((topic: Maybe<Topic>, _index: number, _array: Maybe<Topic>[]) => {
|
|
if (topic) {
|
|
if (!result[topic.slug]) {
|
|
result[topic.slug] = []
|
|
}
|
|
if (authors) {
|
|
authors.forEach((author) => {
|
|
if (!result[topic.slug].some((a) => a.slug === author?.slug)) {
|
|
result[topic.slug].push(author as Author)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
return result
|
|
})
|
|
|
|
const loadAllAuthors = async () => {
|
|
const fetcher = loadAuthorsAll()
|
|
const data = await fetcher()
|
|
addAuthors(data || [])
|
|
return data || []
|
|
}
|
|
|
|
const contextValue: AuthorsContextType = {
|
|
loadAllAuthors,
|
|
authorsEntities,
|
|
authorsSorted,
|
|
addAuthors,
|
|
addAuthor,
|
|
loadAuthor,
|
|
loadAuthors: loadAuthorsPage,
|
|
topAuthors,
|
|
authorsByTopic,
|
|
setAuthorsSort
|
|
}
|
|
|
|
return <AuthorsContext.Provider value={contextValue}>{props.children}</AuthorsContext.Provider>
|
|
}
|