webapp/src/context/topics.tsx

132 lines
3.6 KiB
TypeScript
Raw Normal View History

2024-05-06 23:44:25 +00:00
import { createLazyMemo } from '@solid-primitives/memo'
2024-03-18 11:07:28 +00:00
import { openDB } from 'idb'
2024-05-06 23:44:25 +00:00
import { Accessor, JSX, createContext, createMemo, createSignal, onMount, useContext } from 'solid-js'
2024-03-18 11:07:28 +00:00
import { apiClient } from '../graphql/client/core'
import { Topic } from '../graphql/schema/core.gen'
2024-05-06 23:44:25 +00:00
import { useRouter } from '../stores/router'
import { byTopicStatDesc } from '../utils/sortby'
2024-03-18 11:07:28 +00:00
type TopicsContextType = {
2024-05-06 23:44:25 +00:00
topicEntities: Accessor<{ [topicSlug: string]: Topic }>
sortedTopics: Accessor<Topic[]>
randomTopics: Accessor<Topic[]>
topTopics: Accessor<Topic[]>
setTopicsSort: (sortBy: string) => void
addTopics: (topics: Topic[]) => void
2024-03-18 11:07:28 +00:00
}
const TopicsContext = createContext<TopicsContextType>()
export function useTopics() {
return useContext(TopicsContext)
}
const DB_NAME = 'discourseAppDB'
const DB_VERSION = 1
const STORE_NAME = 'topics'
const setupIndexedDB = async () => {
return await openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'id' })
}
},
})
}
2024-05-05 16:13:48 +00:00
const getTopicsFromIndexedDB = (db) => {
2024-03-18 11:07:28 +00:00
const tx = db.transaction(STORE_NAME, 'readonly')
const store = tx.objectStore(STORE_NAME)
return store.getAll()
}
const saveTopicsToIndexedDB = async (db, topics) => {
const tx = db.transaction(STORE_NAME, 'readwrite')
const store = tx.objectStore(STORE_NAME)
for (const topic of topics) {
await store.put(topic)
}
await tx.done
}
export const TopicsProvider = (props: { children: JSX.Element }) => {
2024-05-06 23:44:25 +00:00
const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({})
const [sortAllBy, setSortAllBy] = createSignal<'shouts' | 'followers' | 'authors' | 'title'>('shouts')
2024-03-18 11:07:28 +00:00
const [randomTopics, setRandomTopics] = createSignal<Topic[]>([])
2024-05-06 23:44:25 +00:00
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,
}
})
}
2024-03-18 11:07:28 +00:00
onMount(async () => {
const db = await setupIndexedDB()
2024-05-06 23:44:25 +00:00
let topics = await getTopicsFromIndexedDB(db)
2024-03-18 11:07:28 +00:00
2024-05-06 23:44:25 +00:00
if (topics.length < 100) {
2024-03-18 11:07:28 +00:00
topics = await apiClient.getAllTopics()
await saveTopicsToIndexedDB(db, topics)
}
2024-05-06 23:44:25 +00:00
addTopics(topics)
2024-03-18 11:07:28 +00:00
setRandomTopics(topics)
})
2024-05-06 23:44:25 +00:00
const value: TopicsContextType = {
setTopicsSort: setSortAllBy,
topicEntities,
sortedTopics,
randomTopics,
topTopics,
addTopics,
}
2024-03-18 11:07:28 +00:00
return <TopicsContext.Provider value={value}>{props.children}</TopicsContext.Provider>
}