webapp/src/components/Views/Expo/Expo.tsx

250 lines
8.8 KiB
TypeScript
Raw Normal View History

import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
2024-02-04 11:25:21 +00:00
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
2023-10-10 15:38:02 +00:00
import { useLocalize } from '../../../context/localize'
2023-12-18 01:15:49 +00:00
import { apiClient } from '../../../graphql/client/core'
import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '../../../graphql/schema/core.gen'
2023-10-10 15:38:02 +00:00
import { LayoutType } from '../../../pages/types'
import { router } from '../../../stores/router'
2023-10-10 15:38:02 +00:00
import { loadShouts, resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles'
2023-12-18 01:15:49 +00:00
import { getUnixtime } from '../../../utils/getServerDate'
2023-10-10 15:38:02 +00:00
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
import { splitToPages } from '../../../utils/splitToPages'
2024-02-04 11:25:21 +00:00
import { ArticleCard } from '../../Feed/ArticleCard'
import { Button } from '../../_shared/Button'
2023-10-10 15:38:02 +00:00
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Loading } from '../../_shared/Loading'
import { ArticleCardSwiper } from '../../_shared/SolidSwiper/ArticleCardSwiper'
import styles from './Expo.module.scss'
2023-10-10 15:38:02 +00:00
type Props = {
shouts: Shout[]
layout: LayoutType
2023-10-10 15:38:02 +00:00
}
2024-03-27 00:54:15 +00:00
export const PRERENDERED_ARTICLES_COUNT = 36
const LOAD_MORE_PAGE_SIZE = 12
2023-10-10 15:38:02 +00:00
export const Expo = (props: Props) => {
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.shouts))
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
2024-03-29 17:25:07 +00:00
const [favoriteTopArticles, setFavoriteTopArticles] = createSignal<Shout[]>([])
const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal<Shout[]>([])
2023-10-10 15:38:02 +00:00
const { t } = useLocalize()
2023-10-10 15:38:02 +00:00
const { sortedArticles } = useArticlesStore({
2024-03-29 13:53:26 +00:00
shouts: isLoaded() ? props.shouts : [],
layout: props.layout,
2023-10-10 15:38:02 +00:00
})
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
2024-03-29 17:25:07 +00:00
const filters = { ...additionalFilters }
2024-01-21 09:25:38 +00:00
if (!filters.layouts) filters.layouts = []
if (props.layout) {
2023-12-18 01:15:49 +00:00
filters.layouts.push(props.layout)
} else {
2024-03-29 09:30:38 +00:00
filters.layouts.push('audio', 'video', 'image', 'literature')
}
return filters
}
const loadMore = async (count: number) => {
2023-10-10 15:38:02 +00:00
const options: LoadShoutsOptions = {
filters: getLoadShoutsFilters(),
2023-10-10 15:38:02 +00:00
limit: count,
offset: sortedArticles().length,
2023-10-10 15:38:02 +00:00
}
2023-12-18 01:15:49 +00:00
options.filters = props.layout
? { layouts: [props.layout] }
2023-11-28 13:18:25 +00:00
: { layouts: ['audio', 'video', 'image', 'literature'] }
2023-10-10 15:38:02 +00:00
const { hasMore } = await loadShouts(options)
setIsLoadMoreButtonVisible(hasMore)
}
const loadMoreWithoutScrolling = async (count: number) => {
saveScrollPosition()
await loadMore(count)
2023-10-10 15:38:02 +00:00
restoreScrollPosition()
}
const loadRandomTopArticles = async () => {
2023-12-18 01:15:49 +00:00
const options: LoadShoutsOptions = {
2024-03-29 17:25:07 +00:00
filters: { ...getLoadShoutsFilters(), featured: true },
limit: 10,
2023-12-18 01:15:49 +00:00
random_limit: 100,
}
2023-12-18 01:15:49 +00:00
const result = await apiClient.getRandomTopShouts({ options })
2024-03-29 17:25:07 +00:00
setFavoriteTopArticles(result)
}
const loadRandomTopMonthArticles = async () => {
const now = new Date()
2023-12-18 01:15:49 +00:00
const after = getUnixtime(new Date(now.setMonth(now.getMonth() - 1)))
2023-12-18 01:15:49 +00:00
const options: LoadShoutsOptions = {
2024-03-29 17:25:07 +00:00
filters: { ...getLoadShoutsFilters({ after }), reacted: true },
limit: 10,
2023-12-18 01:15:49 +00:00
random_limit: 10,
}
2023-12-18 01:15:49 +00:00
const result = await apiClient.getRandomTopShouts({ options })
2024-03-29 17:25:07 +00:00
setReactedTopMonthArticles(result)
}
2023-10-10 15:38:02 +00:00
onMount(() => {
if (isLoaded()) {
return
}
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
setIsLoaded(true)
})
onMount(() => {
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
loadMore(LOAD_MORE_PAGE_SIZE)
}
loadRandomTopArticles()
loadRandomTopMonthArticles()
2023-10-10 15:38:02 +00:00
})
createEffect(
on(
() => props.layout,
2023-10-10 15:38:02 +00:00
() => {
resetSortedArticles()
2024-03-29 17:25:07 +00:00
setFavoriteTopArticles([])
setReactedTopMonthArticles([])
2023-10-10 15:38:02 +00:00
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
loadRandomTopArticles()
loadRandomTopMonthArticles()
2023-10-10 15:38:02 +00:00
},
),
2023-10-10 15:38:02 +00:00
)
onCleanup(() => {
resetSortedArticles()
})
const handleLoadMoreClick = () => {
2024-03-29 17:25:17 +00:00
loadMoreWithoutScrolling(LOAD_MORE_PAGE_SIZE)
}
2024-01-21 08:56:38 +00:00
2023-10-10 15:38:02 +00:00
return (
<div class={styles.Expo}>
2024-01-15 10:50:43 +00:00
<Show when={sortedArticles()?.length > 0} fallback={<Loading />}>
2023-10-10 15:38:02 +00:00
<div class="wide-container">
<ul class={clsx('view-switcher')}>
<li class={clsx({ 'view-switcher__item--selected': !props.layout })}>
2023-10-10 15:38:02 +00:00
<ConditionalWrapper
condition={Boolean(props.layout)}
wrapper={(children) => <a href={getPagePath(router, 'expo', { layout: '' })}>{children}</a>}
2023-10-10 15:38:02 +00:00
>
<span class={clsx('linkReplacement')}>{t('All')}</span>
</ConditionalWrapper>
</li>
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'literature' })}>
2023-10-10 15:38:02 +00:00
<ConditionalWrapper
condition={props.layout !== 'literature'}
2023-10-10 15:38:02 +00:00
wrapper={(children) => (
<a href={getPagePath(router, 'expo', { layout: 'literature' })}>{children}</a>
2023-10-10 15:38:02 +00:00
)}
>
<span class={clsx('linkReplacement')}>{t('Literature')}</span>
</ConditionalWrapper>
</li>
2023-12-18 01:15:49 +00:00
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'audio' })}>
2023-10-10 15:38:02 +00:00
<ConditionalWrapper
2023-12-18 01:15:49 +00:00
condition={props.layout !== 'audio'}
2023-10-10 15:38:02 +00:00
wrapper={(children) => (
2023-12-18 01:15:49 +00:00
<a href={getPagePath(router, 'expo', { layout: 'audio' })}>{children}</a>
2023-10-10 15:38:02 +00:00
)}
>
<span class={clsx('linkReplacement')}>{t('Music')}</span>
</ConditionalWrapper>
</li>
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'image' })}>
2023-10-10 15:38:02 +00:00
<ConditionalWrapper
condition={props.layout !== 'image'}
2023-10-10 15:38:02 +00:00
wrapper={(children) => (
<a href={getPagePath(router, 'expo', { layout: 'image' })}>{children}</a>
2023-10-10 15:38:02 +00:00
)}
>
<span class={clsx('linkReplacement')}>{t('Gallery')}</span>
</ConditionalWrapper>
</li>
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'video' })}>
2023-10-10 15:38:02 +00:00
<ConditionalWrapper
condition={props.layout !== 'video'}
2023-10-10 15:38:02 +00:00
wrapper={(children) => (
<a href={getPagePath(router, 'expo', { layout: 'video' })}>{children}</a>
2023-10-10 15:38:02 +00:00
)}
>
<span class={clsx('cursorPointer linkReplacement')}>{t('Video')}</span>
</ConditionalWrapper>
</li>
</ul>
<div class="row">
2024-03-29 09:35:01 +00:00
<For each={sortedArticles().slice(0, LOAD_MORE_PAGE_SIZE)}>
{(shout) => (
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
<ArticleCard
article={shout}
settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }}
desktopCoverSize="XS"
2023-12-25 11:35:04 +00:00
withAspectRatio={true}
/>
</div>
)}
</For>
2024-03-29 17:25:07 +00:00
<Show when={reactedTopMonthArticles()?.length > 0} keyed={true}>
<ArticleCardSwiper title={t('Top month articles')} slides={reactedTopMonthArticles()} />
</Show>
2024-03-29 09:35:01 +00:00
<For each={sortedArticles().slice(LOAD_MORE_PAGE_SIZE, LOAD_MORE_PAGE_SIZE * 2)}>
2023-10-10 15:38:02 +00:00
{(shout) => (
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
<ArticleCard
article={shout}
settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }}
desktopCoverSize="XS"
2023-12-25 11:35:04 +00:00
withAspectRatio={true}
2023-10-10 15:38:02 +00:00
/>
</div>
)}
</For>
2024-03-29 17:25:07 +00:00
<Show when={favoriteTopArticles()?.length > 0} keyed={true}>
<ArticleCardSwiper title={t('Favorite')} slides={favoriteTopArticles()} />
</Show>
2024-03-29 13:53:26 +00:00
<For each={sortedArticles().slice(LOAD_MORE_PAGE_SIZE * 2)}>
2024-03-27 00:54:15 +00:00
{(shout) => (
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
<ArticleCard
article={shout}
settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }}
desktopCoverSize="XS"
withAspectRatio={true}
/>
</div>
2023-10-10 15:38:02 +00:00
)}
</For>
</div>
<Show when={isLoadMoreButtonVisible()}>
<div class={styles.showMore}>
<Button size="L" onClick={handleLoadMoreClick} value={t('Load more')} />
</div>
</Show>
</div>
</Show>
</div>
)
}