wip-layouts
This commit is contained in:
parent
fab46a6ef2
commit
30fe44979e
0
src/components/Pages/ArtworksPage.tsx
Normal file
0
src/components/Pages/ArtworksPage.tsx
Normal file
0
src/components/Pages/AudioPage.tsx
Normal file
0
src/components/Pages/AudioPage.tsx
Normal file
48
src/components/Pages/ReadingPage.tsx
Normal file
48
src/components/Pages/ReadingPage.tsx
Normal 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
|
0
src/components/Pages/VIdeoPage.tsx
Normal file
0
src/components/Pages/VIdeoPage.tsx
Normal file
|
@ -31,26 +31,13 @@ import { ThanksPage } from './Pages/about/ThanksPage'
|
|||
import { CreatePage } from './Pages/CreatePage'
|
||||
import { ConnectPage } from './Pages/ConnectPage'
|
||||
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
|
||||
// const HomePage = lazy(() => import('./Pages/HomePage'))
|
||||
// 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 SomePage = lazy(() => import('./Pages/SomePage'))
|
||||
|
||||
const log = getLogger('root')
|
||||
|
||||
|
@ -60,6 +47,10 @@ type RootSearchParams = {
|
|||
}
|
||||
|
||||
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
||||
audio: AudioPage,
|
||||
video: VideoPage,
|
||||
literature: ReadingPage,
|
||||
artworks: ArtworksPage,
|
||||
connect: ConnectPage,
|
||||
create: CreatePage,
|
||||
home: HomePage,
|
||||
|
|
40
src/graphql/query/articles-for-layout.ts
Normal file
40
src/graphql/query/articles-for-layout.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
23
src/pages/layout/[...layout].astro
Normal file
23
src/pages/layout/[...layout].astro
Normal 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
196
src/stores/zine/layouts.ts
Normal 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
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import authorsBySlugs from '../graphql/query/authors-by-slugs'
|
|||
import incrementView from '../graphql/mutation/increment-view'
|
||||
import createArticle from '../graphql/mutation/article-create'
|
||||
import myChats from '../graphql/query/my-chats'
|
||||
import getLayout from '../graphql/query/articles-for-layout'
|
||||
|
||||
const FEED_SIZE = 50
|
||||
|
||||
|
@ -334,5 +335,8 @@ export const apiClient = {
|
|||
getInboxes: async (payload = {}) => {
|
||||
const resp = await privateGraphQLClient.query(myChats, payload).toPromise()
|
||||
return resp.data.myChats
|
||||
},
|
||||
getLayoutShouts: async (layout = 'article', amount = 50, offset = 0) => {
|
||||
const resp = await publicGraphQLClient.query(getLayout, { amount, offset, layout })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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'
|
||||
|
|
Loading…
Reference in New Issue
Block a user