From 56b292c817e3fdf8efa606ec8d8bc4cbf63716cf Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 7 May 2024 02:44:25 +0300 Subject: [PATCH] refactoring: topics context provider --- src/components/Nav/Header/Header.tsx | 2 +- src/components/Views/AllTopics/AllTopics.tsx | 14 ++- src/components/Views/Expo/Expo.tsx | 2 +- src/components/Views/Feed/Feed.tsx | 4 +- src/components/Views/Home.tsx | 6 +- .../Views/PublishSettings/PublishSettings.tsx | 5 +- src/components/Views/Topic.tsx | 6 +- src/context/topics.tsx | 82 ++++++++++++- src/pages/allTopics.page.tsx | 22 +--- src/pages/index.page.tsx | 8 +- src/pages/topic.page.tsx | 2 - src/stores/zine/topics.ts | 110 ------------------ 12 files changed, 103 insertions(+), 160 deletions(-) delete mode 100644 src/stores/zine/topics.ts diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index f1342f1d..680dd886 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -48,7 +48,7 @@ export const Header = (props: Props) => { const { page } = useRouter() const { requireAuthentication } = useSession() const { searchParams } = useRouter() - const { topics } = useTopics() + const { sortedTopics: topics } = useTopics() const [randomTopics, setRandomTopics] = createSignal([]) const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false) const [getIsScrolled, setIsScrolled] = createSignal(false) diff --git a/src/components/Views/AllTopics/AllTopics.tsx b/src/components/Views/AllTopics/AllTopics.tsx index 92e57527..6183c26e 100644 --- a/src/components/Views/AllTopics/AllTopics.tsx +++ b/src/components/Views/AllTopics/AllTopics.tsx @@ -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(() => { diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 8506bf24..cb9fa754 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -192,7 +192,7 @@ export const Expo = (props: Props) => { )} 0} keyed={true}> - + {(shout) => ( diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index e5c196e9..283ebb72 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -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([]) diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx index b4c321ba..ac22c97b 100644 --- a/src/components/Views/Home.tsx +++ b/src/components/Views/Home.tsx @@ -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} /> - + diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 1e9f4a81..de86a02b 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -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(sortedTopics()) @@ -82,7 +82,6 @@ export const PublishSettings = (props: Props) => { onMount(() => { setSettingsForm(initialData()) - loadAllTopics() }) createEffect(() => setTopics(sortedTopics())) diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 97a107dd..1a295bcb 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -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() 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([]) const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal([]) @@ -216,7 +216,7 @@ export const TopicView = (props: Props) => { wrapper={'author'} /> 0} keyed={true}> - + + topicEntities: Accessor<{ [topicSlug: string]: Topic }> + sortedTopics: Accessor + randomTopics: Accessor + topTopics: Accessor + setTopicsSort: (sortBy: string) => void + addTopics: (topics: Topic[]) => void } const TopicsContext = createContext() @@ -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([]) + const sortedTopics = createLazyMemo(() => { + 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, + ) + + 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 {props.children} } diff --git a/src/pages/allTopics.page.tsx b/src/pages/allTopics.page.tsx index 49d54626..5b440afe 100644 --- a/src/pages/allTopics.page.tsx +++ b/src/pages/allTopics.page.tsx @@ -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(props.allTopics)) - - onMount(async () => { - if (isLoaded()) { - return - } - - await loadAllTopics() - setIsLoaded(true) - }) + const { sortedTopics } = useTopics() return ( - + ) } diff --git a/src/pages/index.page.tsx b/src/pages/index.page.tsx index 93d3c412..a04c09b0 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/index.page.tsx @@ -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) }) diff --git a/src/pages/topic.page.tsx b/src/pages/topic.page.tsx index b5e6212a..4fa3fb49 100644 --- a/src/pages/topic.page.tsx +++ b/src/pages/topic.page.tsx @@ -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 () => { diff --git a/src/stores/zine/topics.ts b/src/stores/zine/topics.ts deleted file mode 100644 index 6de17d64..00000000 --- a/src/stores/zine/topics.ts +++ /dev/null @@ -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('shouts') - -export const setTopicsSort = (sortBy: TopicsSortBy) => setSortAllBy(sortBy) - -const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({}) -const [randomTopics, setRandomTopics] = createSignal([]) - -const sortedTopics = createLazyMemo(() => { - 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, - ) - - setTopicEntities((prevTopicEntities) => { - return { - ...prevTopicEntities, - ...newTopicEntities, - } - }) -} - -export const loadAllTopics = async (): Promise => { - const topics = await apiClient.getAllTopics() - addTopics(topics) -} - -export const loadRandomTopics = async ({ amount }: { amount: number }): Promise => { - const topics = await apiClient.getRandomTopics({ amount }) - setRandomTopics(topics) -} - -export const loadTopic = async ({ slug }: { slug: string }): Promise => { - 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 } -}