2023-11-14 15:10:00 +00:00
|
|
|
import { getPagePath } from '@nanostores/router'
|
|
|
|
import { clsx } from 'clsx'
|
2023-10-10 15:38:02 +00:00
|
|
|
import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
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'
|
2023-12-13 22:57:33 +00:00
|
|
|
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'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Button } from '../../_shared/Button'
|
2023-10-10 15:38:02 +00:00
|
|
|
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Loading } from '../../_shared/Loading'
|
2023-12-13 22:57:33 +00:00
|
|
|
import { ArticleCardSwiper } from '../../_shared/SolidSwiper/ArticleCardSwiper'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { ArticleCard } from '../../Feed/ArticleCard'
|
|
|
|
|
|
|
|
import styles from './Expo.module.scss'
|
2023-10-10 15:38:02 +00:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
shouts: Shout[]
|
2023-12-13 22:57:33 +00:00
|
|
|
layout: LayoutType
|
2023-10-10 15:38:02 +00:00
|
|
|
}
|
2023-12-13 22:57:33 +00:00
|
|
|
|
2023-12-14 18:45:50 +00:00
|
|
|
export const PRERENDERED_ARTICLES_COUNT = 24
|
2023-10-10 15:38:02 +00:00
|
|
|
const LOAD_MORE_PAGE_SIZE = 16
|
2023-12-13 22:57:33 +00:00
|
|
|
|
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)
|
|
|
|
|
2023-12-13 22:57:33 +00:00
|
|
|
const [randomTopArticles, setRandomTopArticles] = createSignal<Shout[]>([])
|
|
|
|
const [randomTopMonthArticles, setRandomTopMonthArticles] = createSignal<Shout[]>([])
|
|
|
|
|
2023-10-10 15:38:02 +00:00
|
|
|
const { t } = useLocalize()
|
2023-12-13 22:57:33 +00:00
|
|
|
|
2023-10-10 15:38:02 +00:00
|
|
|
const { sortedArticles } = useArticlesStore({
|
2023-11-14 15:10:00 +00:00
|
|
|
shouts: isLoaded() ? props.shouts : [],
|
2023-10-10 15:38:02 +00:00
|
|
|
})
|
|
|
|
|
2023-12-18 13:22:20 +00:00
|
|
|
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
2023-12-20 08:07:57 +00:00
|
|
|
const filters = { visibility: 'public', ...additionalFilters }
|
2023-12-14 18:45:50 +00:00
|
|
|
|
2023-12-18 01:15:49 +00:00
|
|
|
filters.layouts = []
|
2023-12-14 18:45:50 +00:00
|
|
|
if (props.layout) {
|
2023-12-18 01:15:49 +00:00
|
|
|
filters.layouts.push(props.layout)
|
2023-12-14 18:45:50 +00:00
|
|
|
} else {
|
2023-12-18 01:15:49 +00:00
|
|
|
filters.layouts.push('article')
|
2023-12-14 18:45:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-18 13:22:20 +00:00
|
|
|
return filters
|
2023-12-14 18:45:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 22:57:33 +00:00
|
|
|
const loadMore = async (count: number) => {
|
2023-10-10 15:38:02 +00:00
|
|
|
const options: LoadShoutsOptions = {
|
2023-12-14 18:45:50 +00:00
|
|
|
filters: getLoadShoutsFilters(),
|
2023-10-10 15:38:02 +00:00
|
|
|
limit: count,
|
2023-11-14 15:10:00 +00:00
|
|
|
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)
|
2023-12-16 14:06:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const loadMoreWithoutScrolling = async (count: number) => {
|
|
|
|
saveScrollPosition()
|
|
|
|
await loadMore(count)
|
2023-10-10 15:38:02 +00:00
|
|
|
restoreScrollPosition()
|
|
|
|
}
|
|
|
|
|
2023-12-13 22:57:33 +00:00
|
|
|
const loadRandomTopArticles = async () => {
|
2023-12-18 01:15:49 +00:00
|
|
|
const options: LoadShoutsOptions = {
|
2023-12-14 18:45:50 +00:00
|
|
|
filters: getLoadShoutsFilters(),
|
2023-12-13 22:57:33 +00:00
|
|
|
limit: 10,
|
2023-12-18 01:15:49 +00:00
|
|
|
random_limit: 100,
|
2023-12-13 22:57:33 +00:00
|
|
|
}
|
|
|
|
|
2023-12-18 01:15:49 +00:00
|
|
|
const result = await apiClient.getRandomTopShouts({ options })
|
2023-12-13 22:57:33 +00:00
|
|
|
setRandomTopArticles(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-13 22:57:33 +00:00
|
|
|
|
2023-12-18 01:15:49 +00:00
|
|
|
const options: LoadShoutsOptions = {
|
|
|
|
filters: getLoadShoutsFilters({ after }),
|
2023-12-13 22:57:33 +00:00
|
|
|
limit: 10,
|
2023-12-18 01:15:49 +00:00
|
|
|
random_limit: 10,
|
2023-12-13 22:57:33 +00:00
|
|
|
}
|
|
|
|
|
2023-12-18 01:15:49 +00:00
|
|
|
const result = await apiClient.getRandomTopShouts({ options })
|
2023-12-13 22:57:33 +00:00
|
|
|
setRandomTopMonthArticles(result)
|
|
|
|
}
|
|
|
|
|
2023-10-10 15:38:02 +00:00
|
|
|
const pages = createMemo<Shout[][]>(() =>
|
2023-11-14 15:10:00 +00:00
|
|
|
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE),
|
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)
|
|
|
|
}
|
|
|
|
|
2023-12-13 22:57:33 +00:00
|
|
|
loadRandomTopArticles()
|
|
|
|
loadRandomTopMonthArticles()
|
2023-10-10 15:38:02 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
createEffect(
|
|
|
|
on(
|
2023-12-13 22:57:33 +00:00
|
|
|
() => props.layout,
|
2023-10-10 15:38:02 +00:00
|
|
|
() => {
|
|
|
|
resetSortedArticles()
|
2023-12-13 22:57:33 +00:00
|
|
|
setRandomTopArticles([])
|
|
|
|
setRandomTopMonthArticles([])
|
2023-10-10 15:38:02 +00:00
|
|
|
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
|
2023-12-13 22:57:33 +00:00
|
|
|
loadRandomTopArticles()
|
|
|
|
loadRandomTopMonthArticles()
|
2023-10-10 15:38:02 +00:00
|
|
|
},
|
2023-11-14 15:10:00 +00:00
|
|
|
{ defer: true },
|
|
|
|
),
|
2023-10-10 15:38:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
onCleanup(() => {
|
|
|
|
resetSortedArticles()
|
|
|
|
})
|
|
|
|
|
|
|
|
const handleLoadMoreClick = () => {
|
2023-12-16 14:06:41 +00:00
|
|
|
loadMoreWithoutScrolling(LOAD_MORE_PAGE_SIZE)
|
2023-10-10 15:38:02 +00:00
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div class={styles.Expo}>
|
|
|
|
<Show when={sortedArticles().length > 0} fallback={<Loading />}>
|
|
|
|
<div class="wide-container">
|
|
|
|
<ul class={clsx('view-switcher', styles.navigation)}>
|
2023-12-13 22:57:33 +00:00
|
|
|
<li class={clsx({ 'view-switcher__item--selected': !props.layout })}>
|
2023-10-10 15:38:02 +00:00
|
|
|
<ConditionalWrapper
|
2023-12-13 22:57:33 +00:00
|
|
|
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>
|
2023-12-13 22:57:33 +00:00
|
|
|
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'literature' })}>
|
2023-10-10 15:38:02 +00:00
|
|
|
<ConditionalWrapper
|
2023-12-13 22:57:33 +00:00
|
|
|
condition={props.layout !== 'literature'}
|
2023-10-10 15:38:02 +00:00
|
|
|
wrapper={(children) => (
|
2023-12-13 22:57:33 +00:00
|
|
|
<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>
|
2023-12-13 22:57:33 +00:00
|
|
|
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'image' })}>
|
2023-10-10 15:38:02 +00:00
|
|
|
<ConditionalWrapper
|
2023-12-13 22:57:33 +00:00
|
|
|
condition={props.layout !== 'image'}
|
2023-10-10 15:38:02 +00:00
|
|
|
wrapper={(children) => (
|
2023-12-13 22:57:33 +00:00
|
|
|
<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>
|
2023-12-13 22:57:33 +00:00
|
|
|
<li class={clsx({ 'view-switcher__item--selected': props.layout === 'video' })}>
|
2023-10-10 15:38:02 +00:00
|
|
|
<ConditionalWrapper
|
2023-12-13 22:57:33 +00:00
|
|
|
condition={props.layout !== 'video'}
|
2023-10-10 15:38:02 +00:00
|
|
|
wrapper={(children) => (
|
2023-12-13 22:57:33 +00:00
|
|
|
<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">
|
2023-12-13 22:57:33 +00:00
|
|
|
<For each={sortedArticles().slice(0, PRERENDERED_ARTICLES_COUNT / 2)}>
|
|
|
|
{(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-12-13 22:57:33 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</For>
|
2023-12-16 14:06:41 +00:00
|
|
|
<Show when={randomTopMonthArticles().length > 0} keyed={true}>
|
|
|
|
<ArticleCardSwiper title={t('Top month articles')} slides={randomTopMonthArticles()} />
|
2023-12-13 22:57:33 +00:00
|
|
|
</Show>
|
|
|
|
<For each={sortedArticles().slice(PRERENDERED_ARTICLES_COUNT / 2, PRERENDERED_ARTICLES_COUNT)}>
|
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 }}
|
2023-11-18 14:10:02 +00:00
|
|
|
desktopCoverSize="XS"
|
2023-12-25 11:35:04 +00:00
|
|
|
withAspectRatio={true}
|
2023-10-10 15:38:02 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</For>
|
2023-12-16 14:06:41 +00:00
|
|
|
<Show when={randomTopArticles().length > 0} keyed={true}>
|
|
|
|
<ArticleCardSwiper title={t('Favorite')} slides={randomTopArticles()} />
|
2023-12-13 22:57:33 +00:00
|
|
|
</Show>
|
2023-10-10 15:38:02 +00:00
|
|
|
<For each={pages()}>
|
|
|
|
{(page) => (
|
|
|
|
<For each={page}>
|
|
|
|
{(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 }}
|
2023-11-18 14:10:02 +00:00
|
|
|
desktopCoverSize="XS"
|
2023-12-25 11:35:04 +00:00
|
|
|
withAspectRatio={true}
|
2023-10-10 15:38:02 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
</div>
|
|
|
|
<Show when={isLoadMoreButtonVisible()}>
|
|
|
|
<div class={styles.showMore}>
|
|
|
|
<Button size="L" onClick={handleLoadMoreClick} value={t('Load more')} />
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|