refactoring: topics context provider
This commit is contained in:
parent
a885686ae4
commit
56b292c817
|
@ -48,7 +48,7 @@ export const Header = (props: Props) => {
|
|||
const { page } = useRouter()
|
||||
const { requireAuthentication } = useSession()
|
||||
const { searchParams } = useRouter<HeaderSearchParams>()
|
||||
const { topics } = useTopics()
|
||||
const { sortedTopics: topics } = useTopics()
|
||||
const [randomTopics, setRandomTopics] = createSignal([])
|
||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||
|
|
|
@ -2,10 +2,10 @@ import type { Topic } from '../../../graphql/schema/core.gen'
|
|||
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { clsx } from 'clsx'
|
||||
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'
|
||||
import { For, Show, createEffect, createMemo, createSignal, onMount } from 'solid-js'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useTopics } from '../../../context/topics'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import { setTopicsSort, useTopicsStore } from '../../../stores/zine/topics'
|
||||
import { capitalize } from '../../../utils/capitalize'
|
||||
import { dummyFilter } from '../../../utils/dummyFilter'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
|
@ -33,10 +33,12 @@ export const AllTopics = (props: Props) => {
|
|||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||
const ALPHABET =
|
||||
lang() === 'ru' ? [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#'] : [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ#']
|
||||
|
||||
const { sortedTopics } = useTopicsStore({
|
||||
topics: props.topics,
|
||||
sortBy: searchParams().by || 'shouts',
|
||||
const { sortedTopics, setTopicsSort, addTopics } = useTopics()
|
||||
onMount(() => {
|
||||
setTopicsSort(searchParams()?.by || 'shouts')
|
||||
if (props.topics) {
|
||||
addTopics(props.topics)
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
|
|
|
@ -192,7 +192,7 @@ export const Expo = (props: Props) => {
|
|||
)}
|
||||
</For>
|
||||
<Show when={reactedTopMonthArticles()?.length > 0} keyed={true}>
|
||||
<ArticleCardSwiper title={t('Top month articles')} slides={reactedTopMonthArticles()} />
|
||||
<ArticleCardSwiper title={t('Top month')} slides={reactedTopMonthArticles()} />
|
||||
</Show>
|
||||
<For each={expoShouts().slice(LOAD_MORE_PAGE_SIZE, LOAD_MORE_PAGE_SIZE * 2)}>
|
||||
{(shout) => (
|
||||
|
|
|
@ -6,13 +6,13 @@ import { For, Show, createEffect, createMemo, createSignal, on, onMount } from '
|
|||
import { useLocalize } from '../../../context/localize'
|
||||
import { useReactions } from '../../../context/reactions'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { useTopics } from '../../../context/topics'
|
||||
import { apiClient } from '../../../graphql/client/core'
|
||||
import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/schema/core.gen'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
import { showModal } from '../../../stores/ui'
|
||||
import { resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles'
|
||||
import { useTopAuthorsStore } from '../../../stores/zine/topAuthors'
|
||||
import { useTopicsStore } from '../../../stores/zine/topics'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { byCreated } from '../../../utils/sortby'
|
||||
import { CommentDate } from '../../Article/CommentDate'
|
||||
|
@ -103,7 +103,7 @@ export const FeedView = (props: Props) => {
|
|||
const { session } = useSession()
|
||||
const { loadReactionsBy } = useReactions()
|
||||
const { sortedArticles } = useArticlesStore()
|
||||
const { topTopics } = useTopicsStore()
|
||||
const { topTopics } = useTopics()
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [topComments, setTopComments] = createSignal<Reaction[]>([])
|
||||
|
|
|
@ -2,6 +2,7 @@ import { getPagePath } from '@nanostores/router'
|
|||
import { For, Show, createMemo, createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useTopics } from '../../context/topics'
|
||||
import { Shout, Topic } from '../../graphql/schema/core.gen'
|
||||
import { router } from '../../stores/router'
|
||||
import {
|
||||
|
@ -11,7 +12,6 @@ import {
|
|||
useArticlesStore,
|
||||
} from '../../stores/zine/articles'
|
||||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { capitalize } from '../../utils/capitalize'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
|
@ -46,7 +46,7 @@ export const HomeView = (props: Props) => {
|
|||
shouts: props.shouts,
|
||||
})
|
||||
|
||||
const { topTopics } = useTopicsStore()
|
||||
const { topTopics } = useTopics()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
const { t } = useLocalize()
|
||||
|
@ -110,7 +110,7 @@ export const HomeView = (props: Props) => {
|
|||
nodate={true}
|
||||
/>
|
||||
<Show when={topMonthArticles()}>
|
||||
<ArticleCardSwiper title={t('Top month articles')} slides={topMonthArticles()} />
|
||||
<ArticleCardSwiper title={t('Top month')} slides={topMonthArticles()} />
|
||||
</Show>
|
||||
<Row2 articles={sortedArticles().slice(10, 12)} nodate={true} />
|
||||
<RowShort articles={sortedArticles().slice(12, 16)} />
|
||||
|
|
|
@ -6,11 +6,11 @@ import { createStore } from 'solid-js/store'
|
|||
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { useTopics } from '../../../context/topics'
|
||||
import { Topic } from '../../../graphql/schema/core.gen'
|
||||
import { UploadedFile } from '../../../pages/types'
|
||||
import { router } from '../../../stores/router'
|
||||
import { hideModal, showModal } from '../../../stores/ui'
|
||||
import { loadAllTopics, useTopicsStore } from '../../../stores/zine/topics'
|
||||
import { TopicSelect, UploadModalContent } from '../../Editor'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { Button } from '../../_shared/Button'
|
||||
|
@ -53,7 +53,7 @@ const emptyConfig = {
|
|||
export const PublishSettings = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { author } = useSession()
|
||||
const { sortedTopics } = useTopicsStore()
|
||||
const { sortedTopics } = useTopics()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const [topics, setTopics] = createSignal<Topic[]>(sortedTopics())
|
||||
|
||||
|
@ -82,7 +82,6 @@ export const PublishSettings = (props: Props) => {
|
|||
|
||||
onMount(() => {
|
||||
setSettingsForm(initialData())
|
||||
loadAllTopics()
|
||||
})
|
||||
|
||||
createEffect(() => setTopics(sortedTopics()))
|
||||
|
|
|
@ -5,10 +5,10 @@ import { clsx } from 'clsx'
|
|||
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useTopics } from '../../context/topics'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { capitalize } from '../../utils/capitalize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
import { getDescription } from '../../utils/meta'
|
||||
|
@ -43,7 +43,7 @@ export const TopicView = (props: Props) => {
|
|||
const { searchParams, changeSearchParams } = useRouter<TopicsPageSearchParams>()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
|
||||
const { topicEntities } = useTopicsStore({ topics: [props.topic] })
|
||||
const { topicEntities } = useTopics()
|
||||
const { authorsByTopic } = useAuthorsStore()
|
||||
const [favoriteTopArticles, setFavoriteTopArticles] = createSignal<Shout[]>([])
|
||||
const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal<Shout[]>([])
|
||||
|
@ -216,7 +216,7 @@ export const TopicView = (props: Props) => {
|
|||
wrapper={'author'}
|
||||
/>
|
||||
<Show when={reactedTopMonthArticles()?.length > 0} keyed={true}>
|
||||
<ArticleCardSwiper title={t('Top month articles')} slides={reactedTopMonthArticles()} />
|
||||
<ArticleCardSwiper title={t('Top month')} slides={reactedTopMonthArticles()} />
|
||||
</Show>
|
||||
<Beside
|
||||
beside={sortedArticles()[12]}
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import { createLazyMemo } from '@solid-primitives/memo'
|
||||
import { openDB } from 'idb'
|
||||
import { Accessor, JSX, createContext, createSignal, onMount, useContext } from 'solid-js'
|
||||
import { Accessor, JSX, createContext, createMemo, createSignal, onMount, useContext } from 'solid-js'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
import { Topic } from '../graphql/schema/core.gen'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { byTopicStatDesc } from '../utils/sortby'
|
||||
|
||||
type TopicsContextType = {
|
||||
topics: Accessor<Topic[]>
|
||||
topicEntities: Accessor<{ [topicSlug: string]: Topic }>
|
||||
sortedTopics: Accessor<Topic[]>
|
||||
randomTopics: Accessor<Topic[]>
|
||||
topTopics: Accessor<Topic[]>
|
||||
setTopicsSort: (sortBy: string) => void
|
||||
addTopics: (topics: Topic[]) => void
|
||||
}
|
||||
|
||||
const TopicsContext = createContext<TopicsContextType>()
|
||||
|
@ -40,20 +48,84 @@ const saveTopicsToIndexedDB = async (db, topics) => {
|
|||
}
|
||||
|
||||
export const TopicsProvider = (props: { children: JSX.Element }) => {
|
||||
const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({})
|
||||
const [sortAllBy, setSortAllBy] = createSignal<'shouts' | 'followers' | 'authors' | 'title'>('shouts')
|
||||
const [randomTopics, setRandomTopics] = createSignal<Topic[]>([])
|
||||
|
||||
const sortedTopics = createLazyMemo<Topic[]>(() => {
|
||||
const topics = Object.values(topicEntities())
|
||||
const { changeSearchParams } = useRouter()
|
||||
switch (sortAllBy()) {
|
||||
case 'followers': {
|
||||
topics.sort(byTopicStatDesc('followers'))
|
||||
break
|
||||
}
|
||||
case 'shouts': {
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
break
|
||||
}
|
||||
case 'authors': {
|
||||
topics.sort(byTopicStatDesc('authors'))
|
||||
break
|
||||
}
|
||||
case 'title': {
|
||||
topics.sort((a, b) => a.title.localeCompare(b.title))
|
||||
break
|
||||
}
|
||||
default: {
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
changeSearchParams({ by: 'shouts' })
|
||||
}
|
||||
}
|
||||
|
||||
return topics
|
||||
})
|
||||
|
||||
const topTopics = createMemo(() => {
|
||||
const topics = Object.values(topicEntities())
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
return topics
|
||||
})
|
||||
|
||||
const addTopics = (...args: Topic[][]) => {
|
||||
const allTopics = args.flatMap((topics) => (topics || []).filter(Boolean))
|
||||
|
||||
const newTopicEntities = allTopics.reduce(
|
||||
(acc, topic) => {
|
||||
acc[topic.slug] = topic
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Topic>,
|
||||
)
|
||||
|
||||
setTopicEntities((prevTopicEntities) => {
|
||||
return {
|
||||
...prevTopicEntities,
|
||||
...newTopicEntities,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const db = await setupIndexedDB()
|
||||
let topics = getTopicsFromIndexedDB(db)
|
||||
let topics = await getTopicsFromIndexedDB(db)
|
||||
|
||||
if (topics.length === 0) {
|
||||
if (topics.length < 100) {
|
||||
topics = await apiClient.getAllTopics()
|
||||
await saveTopicsToIndexedDB(db, topics)
|
||||
}
|
||||
addTopics(topics)
|
||||
setRandomTopics(topics)
|
||||
})
|
||||
|
||||
const value: TopicsContextType = { topics: randomTopics }
|
||||
const value: TopicsContextType = {
|
||||
setTopicsSort: setSortAllBy,
|
||||
topicEntities,
|
||||
sortedTopics,
|
||||
randomTopics,
|
||||
topTopics,
|
||||
addTopics,
|
||||
}
|
||||
|
||||
return <TopicsContext.Provider value={value}>{props.children}</TopicsContext.Provider>
|
||||
}
|
||||
|
|
|
@ -1,29 +1,15 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { AllTopics } from '../components/Views/AllTopics'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { loadAllTopics } from '../stores/zine/topics'
|
||||
import { useTopics } from '../context/topics'
|
||||
|
||||
export const AllTopicsPage = (props: PageProps) => {
|
||||
export const AllTopicsPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allTopics))
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadAllTopics()
|
||||
setIsLoaded(true)
|
||||
})
|
||||
const { sortedTopics } = useTopics()
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Themes and plots')}>
|
||||
<AllTopics isLoaded={isLoaded()} topics={props.allTopics} />
|
||||
<AllTopics isLoaded={!!sortedTopics()?.length} topics={sortedTopics()} />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@ import type { PageProps } from './types'
|
|||
|
||||
import { Show, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
|
||||
import { HomeView, PRERENDERED_ARTICLES_COUNT, RANDOM_TOPICS_COUNT } from '../components/Views/Home'
|
||||
import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
import { loadRandomTopics } from '../stores/zine/topics'
|
||||
|
||||
export const HomePage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeShouts))
|
||||
|
@ -19,10 +18,7 @@ export const HomePage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadShouts({ filters: { featured: true }, limit: PRERENDERED_ARTICLES_COUNT }),
|
||||
loadRandomTopics({ amount: RANDOM_TOPICS_COUNT }),
|
||||
])
|
||||
await loadShouts({ filters: { featured: true }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
|
|
@ -8,7 +8,6 @@ import { PageLayout } from '../components/_shared/PageLayout'
|
|||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
import { loadTopic } from '../stores/zine/topics'
|
||||
|
||||
export const TopicPage = (props: PageProps) => {
|
||||
const { page } = useRouter()
|
||||
|
@ -25,7 +24,6 @@ export const TopicPage = (props: PageProps) => {
|
|||
limit: PRERENDERED_ARTICLES_COUNT,
|
||||
offset: 0,
|
||||
}),
|
||||
loadTopic({ slug: slug() }),
|
||||
])
|
||||
|
||||
onMount(async () => {
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
import type { Topic } from '../../graphql/schema/core.gen'
|
||||
|
||||
import { createLazyMemo } from '@solid-primitives/memo'
|
||||
import { createMemo, createSignal } from 'solid-js'
|
||||
|
||||
import { apiClient } from '../../graphql/client/core'
|
||||
import { byTopicStatDesc } from '../../utils/sortby'
|
||||
import { useRouter } from '../router'
|
||||
|
||||
export type TopicsSortBy = 'followers' | 'title' | 'authors' | 'shouts'
|
||||
|
||||
const [sortAllBy, setSortAllBy] = createSignal<TopicsSortBy>('shouts')
|
||||
|
||||
export const setTopicsSort = (sortBy: TopicsSortBy) => setSortAllBy(sortBy)
|
||||
|
||||
const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({})
|
||||
const [randomTopics, setRandomTopics] = createSignal<Topic[]>([])
|
||||
|
||||
const sortedTopics = createLazyMemo<Topic[]>(() => {
|
||||
const topics = Object.values(topicEntities())
|
||||
const { changeSearchParams } = useRouter()
|
||||
switch (sortAllBy()) {
|
||||
case 'followers': {
|
||||
// console.debug('[store.topics] sorted by followers')
|
||||
topics.sort(byTopicStatDesc('followers'))
|
||||
break
|
||||
}
|
||||
case 'shouts': {
|
||||
// log.debug(`sorted by shouts`)
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
break
|
||||
}
|
||||
case 'authors': {
|
||||
// log.debug(`sorted by authors`)
|
||||
topics.sort(byTopicStatDesc('authors'))
|
||||
break
|
||||
}
|
||||
case 'title': {
|
||||
// console.debug('[store.topics] sorted by title')
|
||||
topics.sort((a, b) => a.title.localeCompare(b.title))
|
||||
break
|
||||
}
|
||||
default: {
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
changeSearchParams({ by: 'shouts' })
|
||||
}
|
||||
}
|
||||
|
||||
return topics
|
||||
})
|
||||
|
||||
const topTopics = createMemo(() => {
|
||||
const topics = Object.values(topicEntities())
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
return topics
|
||||
})
|
||||
|
||||
const addTopics = (...args: Topic[][]) => {
|
||||
const allTopics = args.flatMap((topics) => (topics || []).filter(Boolean))
|
||||
|
||||
const newTopicEntities = allTopics.reduce(
|
||||
(acc, topic) => {
|
||||
acc[topic.slug] = topic
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Topic>,
|
||||
)
|
||||
|
||||
setTopicEntities((prevTopicEntities) => {
|
||||
return {
|
||||
...prevTopicEntities,
|
||||
...newTopicEntities,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const loadAllTopics = async (): Promise<void> => {
|
||||
const topics = await apiClient.getAllTopics()
|
||||
addTopics(topics)
|
||||
}
|
||||
|
||||
export const loadRandomTopics = async ({ amount }: { amount: number }): Promise<void> => {
|
||||
const topics = await apiClient.getRandomTopics({ amount })
|
||||
setRandomTopics(topics)
|
||||
}
|
||||
|
||||
export const loadTopic = async ({ slug }: { slug: string }): Promise<void> => {
|
||||
const topic = await apiClient.getTopic({ slug })
|
||||
addTopics([topic])
|
||||
}
|
||||
|
||||
type InitialState = {
|
||||
topics?: Topic[]
|
||||
randomTopics?: Topic[]
|
||||
sortBy?: TopicsSortBy
|
||||
}
|
||||
|
||||
export const useTopicsStore = (initialState: InitialState = {}) => {
|
||||
if (initialState.sortBy) {
|
||||
setSortAllBy(initialState.sortBy)
|
||||
}
|
||||
|
||||
addTopics(initialState.topics, initialState.randomTopics)
|
||||
|
||||
if (initialState.randomTopics) {
|
||||
setRandomTopics(initialState.randomTopics)
|
||||
}
|
||||
|
||||
return { topicEntities, sortedTopics, randomTopics, topTopics }
|
||||
}
|
Loading…
Reference in New Issue
Block a user