wip-layouts

This commit is contained in:
tonyrewin 2022-11-12 21:14:20 +03:00
parent fab46a6ef2
commit 30fe44979e
10 changed files with 322 additions and 19 deletions

View File

View File

View File

@ -0,0 +1,48 @@
import { MainLayout } from '../Layouts/MainLayout'
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { loadLayoutShouts, loadLayout } from '../../stores/zine/layouts'
import { Loading } from '../Loading'
export const ReadingPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
const slug = createMemo(() => {
const { page: getPage } = useRouter()
const page = getPage()
if (page.route !== 'topic') {
throw new Error('ts guard')
}
return page.params.slug
})
onMount(async () => {
if (isLoaded()) {
return
}
await loadLayoutShouts({ topicSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadLayout({ slug: slug() })
setIsLoaded(true)
})
onCleanup(() => resetSortedArticles())
return (
<MainLayout>
<Show when={isLoaded()} fallback={<Loading />}>
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
</Show>
</MainLayout>
)
}
// for lazy loading
export default TopicPage

View File

View File

@ -31,26 +31,13 @@ import { ThanksPage } from './Pages/about/ThanksPage'
import { CreatePage } from './Pages/CreatePage' import { CreatePage } from './Pages/CreatePage'
import { ConnectPage } from './Pages/ConnectPage' import { ConnectPage } from './Pages/ConnectPage'
import { renewSession } from '../stores/auth' import { renewSession } from '../stores/auth'
import { AudioPage } from './Pages/AudioPage'
import { VideoPage } from './Pages/VideoPage'
import { ReadingPage } from './Pages/ReadingPage'
import { ArtworksPage } from './Pages/ArtworksPage'
// TODO: lazy load // TODO: lazy load
// const HomePage = lazy(() => import('./Pages/HomePage')) // const SomePage = lazy(() => import('./Pages/SomePage'))
// const AllTopicsPage = lazy(() => import('./Pages/AllTopicsPage'))
// const TopicPage = lazy(() => import('./Pages/TopicPage'))
// const AllAuthorsPage = lazy(() => import('./Pages/AllAuthorsPage'))
// const AuthorPage = lazy(() => import('./Pages/AuthorPage'))
// const FeedPage = lazy(() => import('./Pages/FeedPage'))
// const ArticlePage = lazy(() => import('./Pages/ArticlePage'))
// const SearchPage = lazy(() => import('./Pages/SearchPage'))
// const FourOuFourPage = lazy(() => import('./Pages/FourOuFourPage'))
// const DogmaPage = lazy(() => import('./Pages/about/DogmaPage'))
// const GuidePage = lazy(() => import('./Pages/about/GuidePage'))
// const HelpPage = lazy(() => import('./Pages/about/HelpPage'))
// const ManifestPage = lazy(() => import('./Pages/about/ManifestPage'))
// const PartnersPage = lazy(() => import('./Pages/about/PartnersPage'))
// const ProjectsPage = lazy(() => import('./Pages/about/ProjectsPage'))
// const TermsOfUsePage = lazy(() => import('./Pages/about/TermsOfUsePage'))
// const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
// const CreatePage = lazy(() => import('./Pages/about/CreatePage'))
const log = getLogger('root') const log = getLogger('root')
@ -60,6 +47,10 @@ type RootSearchParams = {
} }
const pagesMap: Record<keyof Routes, Component<PageProps>> = { const pagesMap: Record<keyof Routes, Component<PageProps>> = {
audio: AudioPage,
video: VideoPage,
literature: ReadingPage,
artworks: ArtworksPage,
connect: ConnectPage, connect: ConnectPage,
create: CreatePage, create: CreatePage,
home: HomePage, home: HomePage,

View File

@ -0,0 +1,40 @@
import { gql } from '@urql/core'
export default gql`
query ShoutsForLayoutQuery($amount: Int, $offset: Int, $layout: String) {
shoutsByLayout(amount: $amount, offset: $offset, layout: $layout) {
_id: slug
title
subtitle
layout
slug
cover
# community
mainTopic
topics {
title
body
slug
stat {
_id: shouts
shouts
authors
followers
}
}
authors {
_id: slug
name
slug
userpic
}
createdAt
publishedAt
stat {
_id: viewed
viewed
reacted
}
}
}
`

View File

@ -0,0 +1,23 @@
---
import { Root } from '../../components/Root'
import Zine from '../../layouts/zine.astro'
import { apiClient } from '../../utils/apiClient'
import { initRouter } from '../../stores/router'
const layout = Astro.params.layout?.toString()
if (layout.endsWith('.map')) {
return Astro.redirect('/404')
}
const LAYOUTS = ['literature', 'audio', 'video', 'artworks']
if (!LAYOUTS.includes(layout)) {
return Astro.redirect('/404')
}
const shouts = await apiClient.getRecentByLayout(layout)
const { pathname, search } = Astro.url
initRouter(pathname, search)
---
<Zine>
<Root shouts={shouts} layout={layout} client:load />
</Zine>

196
src/stores/zine/layouts.ts Normal file
View File

@ -0,0 +1,196 @@
import type { Author, Shout, ShoutInput, Topic } from '../../graphql/types.gen'
import { apiClient } from '../../utils/apiClient'
import { addAuthorsByTopic } from './authors'
import { addTopicsByAuthor } from './topics'
import { byStat } from '../../utils/sortby'
import { createSignal } from 'solid-js'
import { createLazyMemo } from '@solid-primitives/memo'
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
const [topArticles, setTopArticles] = createSignal<Shout[]>([])
const [topMonthArticles, setTopMonthArticles] = createSignal<Shout[]>([])
const articlesByLayout = createLazyMemo(() => {
return Object.values(articleEntities()).reduce((acc, article) => {
if (!acc[article.layout]) {
acc[article.layout] = []
}
acc[article.layout].push(article)
return acc
}, {} as { [layout: string]: Shout[] })
})
const topViewedArticles = createLazyMemo(() => {
const result = Object.values(articleEntities())
result.sort(byStat('viewed'))
return result
})
const topCommentedArticles = createLazyMemo(() => {
const result = Object.values(articleEntities())
result.sort(byStat('commented'))
return result
})
// eslint-disable-next-line sonarjs/cognitive-complexity
const addArticles = (...args: Shout[][]) => {
const allArticles = args.flatMap((articles) => articles || [])
const newArticleEntities = allArticles.reduce((acc, article) => {
acc[article.slug] = article
return acc
}, {} as { [articleSLug: string]: Shout })
setArticleEntities((prevArticleEntities) => {
return {
...prevArticleEntities,
...newArticleEntities
}
})
const authorsByTopic = allArticles.reduce((acc, article) => {
const { authors, topics } = article
topics.forEach((topic) => {
if (!acc[topic.slug]) {
acc[topic.slug] = []
}
authors.forEach((author) => {
if (!acc[topic.slug].some((a) => a.slug === author.slug)) {
acc[topic.slug].push(author)
}
})
})
return acc
}, {} as { [topicSlug: string]: Author[] })
addAuthorsByTopic(authorsByTopic)
const topicsByAuthor = allArticles.reduce((acc, article) => {
const { authors, topics } = article
authors.forEach((author) => {
if (!acc[author.slug]) {
acc[author.slug] = []
}
topics.forEach((topic) => {
if (!acc[author.slug].some((t) => t.slug === topic.slug)) {
acc[author.slug].push(topic)
}
})
})
return acc
}, {} as { [authorSlug: string]: Topic[] })
addTopicsByAuthor(topicsByAuthor)
}
const addSortedArticles = (articles: Shout[]) => {
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
}
export const loadLayoutShouts = async ({
layout,
limit,
offset
}: {
layout: string
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getLayoutShouts({ layout, limit: limit + 1, offset })
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const resetSortedArticles = () => {
setSortedArticles([])
}
export const loadTopMonthArticles = async (): Promise<void> => {
const articles = await apiClient.getTopMonthArticles()
addArticles(articles)
setTopMonthArticles(articles)
}
export const loadTopArticles = async (): Promise<void> => {
const articles = await apiClient.getTopArticles()
addArticles(articles)
setTopArticles(articles)
}
export const loadSearchResults = async ({
query,
limit,
offset
}: {
query: string
limit?: number
offset?: number
}): Promise<void> => {
const newArticles = await apiClient.getSearchResults({ query, limit, offset })
addArticles(newArticles)
addSortedArticles(newArticles)
}
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
await apiClient.incrementView({ articleSlug })
}
export const loadArticle = async ({ slug }: { slug: string }): Promise<void> => {
const article = await apiClient.getArticle({ slug })
if (!article) {
throw new Error(`Can't load article, slug: "${slug}"`)
}
addArticles([article])
}
export const createArticle = async ({ article }: { article: ShoutInput }) => {
try {
await apiClient.createArticle({ article })
} catch (error) {
console.error(error)
}
}
type InitialState = {
sortedArticles?: Shout[]
topRatedArticles?: Shout[]
topRatedMonthArticles?: Shout[]
}
export const useArticlesStore = (initialState: InitialState = {}) => {
addArticles([...(initialState.sortedArticles || [])])
if (initialState.sortedArticles) {
setSortedArticles([...initialState.sortedArticles])
}
return {
articleEntities,
sortedArticles,
topArticles,
topMonthArticles,
topViewedArticles,
topCommentedArticles,
articlesByLayout
}
}

View File

@ -29,6 +29,7 @@ import authorsBySlugs from '../graphql/query/authors-by-slugs'
import incrementView from '../graphql/mutation/increment-view' import incrementView from '../graphql/mutation/increment-view'
import createArticle from '../graphql/mutation/article-create' import createArticle from '../graphql/mutation/article-create'
import myChats from '../graphql/query/my-chats' import myChats from '../graphql/query/my-chats'
import getLayout from '../graphql/query/articles-for-layout'
const FEED_SIZE = 50 const FEED_SIZE = 50
@ -334,5 +335,8 @@ export const apiClient = {
getInboxes: async (payload = {}) => { getInboxes: async (payload = {}) => {
const resp = await privateGraphQLClient.query(myChats, payload).toPromise() const resp = await privateGraphQLClient.query(myChats, payload).toPromise()
return resp.data.myChats return resp.data.myChats
},
getLayoutShouts: async (layout = 'article', amount = 50, offset = 0) => {
const resp = await publicGraphQLClient.query(getLayout, { amount, offset, layout })
} }
} }

View File

@ -1,4 +1,5 @@
export const isDev = import.meta.env.MODE === 'development' export const isDev = import.meta.env.MODE === 'development'
export const apiBaseUrl = 'https://newapi.discours.io' // export const apiBaseUrl = 'https://newapi.discours.io'
export const apiBaseUrl = 'https://testapi.discours.io'
// export const apiBaseUrl = 'http://localhost:8080' // export const apiBaseUrl = 'http://localhost:8080'