diff --git a/src/components/Views/Expo/Expo.module.scss b/src/components/Views/Expo/Expo.module.scss index 190b4243..03b02dbe 100644 --- a/src/components/Views/Expo/Expo.module.scss +++ b/src/components/Views/Expo/Expo.module.scss @@ -3,12 +3,4 @@ background: #fef2f2; padding: 0 0 4rem; min-height: 100vh; - - .showMore { - display: flex; - width: 100%; - padding: 4rem 0 2rem; - align-items: center; - justify-content: center; - } } diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index fefd6924..cf9e4066 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -1,47 +1,34 @@ -import { A } from '@solidjs/router' -import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' +import { For, Show, createEffect, createSignal, on } from 'solid-js' -import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' -import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' import { Loading } from '~/components/_shared/Loading' import { ArticleCardSwiper } from '~/components/_shared/SolidSwiper/ArticleCardSwiper' -import { EXPO_LAYOUTS, SHOUTS_PER_PAGE, useFeed } from '~/context/feed' +import { EXPO_LAYOUTS, SHOUTS_PER_PAGE } from '~/context/feed' import { useLocalize } from '~/context/localize' import { useSession } from '~/context/session' -import { loadShouts } from '~/graphql/api/public' import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top' -import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' -import { LayoutType } from '~/types/common' +import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' +import { ExpoLayoutType } from '~/types/common' import { getUnixtime } from '~/utils/date' -import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' -import { byCreated } from '~/utils/sort' import { ArticleCard } from '../../Feed/ArticleCard' import styles from './Expo.module.scss' type Props = { shouts: Shout[] - topMonthShouts?: Shout[] - topRatedShouts?: Shout[] - layout?: LayoutType + layout: ExpoLayoutType } -export const PRERENDERED_ARTICLES_COUNT = 36 -const LOAD_MORE_PAGE_SIZE = 12 - export const Expo = (props: Props) => { const { t } = useLocalize() const { client } = useSession() - const [favoriteTopArticles, setFavoriteTopArticles] = createSignal([]) const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal([]) - const { feedByLayout, expoFeed, setExpoFeed } = useFeed() - const layouts = createMemo(() => (props.layout ? [props.layout] : EXPO_LAYOUTS)) + // Функция загрузки случайных избранных статей const loadRandomTopArticles = async () => { + const layouts = props.layout ? [props.layout] : EXPO_LAYOUTS const options: LoadShoutsOptions = { - filters: { layouts: layouts(), featured: true }, + filters: { layouts, featured: true }, limit: 10, random_limit: 100 } @@ -49,11 +36,13 @@ export const Expo = (props: Props) => { setFavoriteTopArticles(resp?.data?.load_shouts_random_top || []) } + // Функция загрузки популярных статей за последний месяц const loadRandomTopMonthArticles = async () => { + const layouts = props.layout ? [props.layout] : EXPO_LAYOUTS const now = new Date() const after = getUnixtime(new Date(now.setMonth(now.getMonth() - 1))) const options: LoadShoutsOptions = { - filters: { layouts: layouts(), after, reacted: true }, + filters: { layouts, after, reacted: true }, limit: 10, random_limit: 10 } @@ -61,141 +50,46 @@ export const Expo = (props: Props) => { setReactedTopMonthArticles(resp?.data?.load_shouts_random_top || []) } - onMount(() => { - loadRandomTopArticles() - loadRandomTopMonthArticles() - }) - + // Эффект для загрузки random top при изменении layout createEffect( - on(layouts, (lll) => { - console.debug('layouts changed', lll) - loadRandomTopArticles() - loadRandomTopMonthArticles() - }) + on( + () => props.layout, + async (_layout?: ExpoLayoutType) => { + await loadRandomTopArticles() + await loadRandomTopMonthArticles() + } + ) ) - onCleanup(() => { - setExpoFeed([]) - }) - const ExpoTabs = () => ( -
- -
- ) - const ExpoGrid = (props: Props) => ( -
-
- - {(shout) => ( -
- -
- )} -
- 0} keyed={true}> - - - - {(shout) => ( -
- -
- )} -
- 0} keyed={true}> - - - - {(shout) => ( -
- -
- )} -
-
-
- ) - - const [loadMoreVisible, setLoadMoreVisible] = createSignal(false) - - // дозагрузка - const loadMore = async () => { - saveScrollPosition() - const limit = SHOUTS_PER_PAGE - const offset = (props.layout ? feedByLayout()[props.layout] : expoFeed())?.length - const filters: LoadShoutsFilters = { layouts: layouts(), featured: true } - const options: LoadShoutsOptions = { filters, limit, offset } - const shoutsFetcher = loadShouts(options) - const result = await shoutsFetcher() - setLoadMoreVisible(Boolean(result?.length)) - const expoFeedUpdater = (layout?: LayoutType) => (prev: Shout[]) => - Array.from(new Set((layout ? prev || [] : expoFeed())?.concat(result || [])))?.sort(byCreated) - result && setExpoFeed(expoFeedUpdater(props.layout)) - restoreScrollPosition() - return result as LoadMoreItems - } - return (
- + } keyed> + {(feed: Shout[]) => ( +
+
+ + {(shout) => ( +
+ +
+ )} +
+
- }> - + 0}> + + + + 0}> + + +
+ )}
) diff --git a/src/components/Views/Expo/ExpoNav.tsx b/src/components/Views/Expo/ExpoNav.tsx new file mode 100644 index 00000000..2bc1933d --- /dev/null +++ b/src/components/Views/Expo/ExpoNav.tsx @@ -0,0 +1,34 @@ +import { A } from '@solidjs/router' +import { clsx } from 'clsx' +import { For } from 'solid-js' + +import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' +import { EXPO_LAYOUTS, EXPO_TITLES } from '~/context/feed' +import { useLocalize } from '~/context/localize' +import { ExpoLayoutType } from '~/types/common' + +export const ExpoNav = (props: { layout: ExpoLayoutType | '' }) => { + const { t } = useLocalize() + return ( +
+
    + + {(layoutKey) => ( +
  • + {children}} + > + + {layoutKey in EXPO_TITLES ? t(EXPO_TITLES[layoutKey as ExpoLayoutType]) : t('All')} + + +
  • + )} +
    +
+
+ ) +} + +export default ExpoNav diff --git a/src/components/_shared/LoadMoreWrapper.tsx b/src/components/_shared/LoadMoreWrapper.tsx index ff53e9bd..493e94ef 100644 --- a/src/components/_shared/LoadMoreWrapper.tsx +++ b/src/components/_shared/LoadMoreWrapper.tsx @@ -33,6 +33,7 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => { ) const loadItems = async () => { + // console.debug('LoadMoreWrapper.loadItems offset:', offset()) setIsLoading(true) saveScrollPosition() const newItems = await props.loadFunction(offset()) @@ -47,6 +48,7 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => { ) setIsLoading(false) restoreScrollPosition() + // console.debug('LoadMoreWrapper.loadItems loaded:', newItems.length) } onMount(loadItems) @@ -54,16 +56,18 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => { return ( <> {props.children} - -
-
-
+
+ +
+
+
+
) } diff --git a/src/context/feed.tsx b/src/context/feed.tsx index 7ce2c0c2..b6d22375 100644 --- a/src/context/feed.tsx +++ b/src/context/feed.tsx @@ -10,13 +10,20 @@ import { Shout, Topic } from '~/graphql/schema/core.gen' -import { LayoutType } from '~/types/common' +import { ExpoLayoutType } from '~/types/common' import { byStat } from '../utils/sort' import { useSession } from './session' export const PRERENDERED_ARTICLES_COUNT = 5 export const SHOUTS_PER_PAGE = 20 -export const EXPO_LAYOUTS = ['audio', 'literature', 'video', 'image'] as LayoutType[] +export const EXPO_LAYOUTS = ['audio', 'literature', 'video', 'image'] as ExpoLayoutType[] +export const EXPO_TITLES: Record = { + 'audio': 'Audio', + 'video': 'Video', + 'image': 'Artworks', + 'literature': 'Literature', + '': 'All' +} type FeedContextType = { sortedFeed: Accessor diff --git a/src/routes/expo/[...layout].tsx b/src/routes/expo/[...layout].tsx index 764fc186..f0410caf 100644 --- a/src/routes/expo/[...layout].tsx +++ b/src/routes/expo/[...layout].tsx @@ -1,13 +1,17 @@ import { Params, RouteSectionProps, createAsync } from '@solidjs/router' -import { Show, onMount } from 'solid-js' +import { Show, createEffect, createSignal, on } from 'solid-js' import { TopicsNav } from '~/components/TopicsNav' import { Expo } from '~/components/Views/Expo' +import ExpoNav from '~/components/Views/Expo/ExpoNav' +import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' import { PageLayout } from '~/components/_shared/PageLayout' -import { EXPO_LAYOUTS, SHOUTS_PER_PAGE } from '~/context/feed' +import { EXPO_LAYOUTS, EXPO_TITLES, SHOUTS_PER_PAGE, useFeed } from '~/context/feed' import { useLocalize } from '~/context/localize' import { loadShouts } from '~/graphql/api/public' -import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' -import { LayoutType } from '~/types/common' +import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' +import { ExpoLayoutType } from '~/types/common' +import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' +import { byCreated } from '~/utils/sort' const fetchExpoShouts = async (layouts: string[]) => { const result = await loadShouts({ @@ -28,35 +32,50 @@ export const route = { export default (props: RouteSectionProps) => { const { t } = useLocalize() + const { expoFeed, setExpoFeed, feedByLayout } = useFeed() + const [loadMoreVisible, setLoadMoreVisible] = createSignal(false) + const getTitle = (l?: string) => EXPO_TITLES[(l as ExpoLayoutType) || ''] + const shouts = createAsync( async () => props.data || (await fetchExpoShouts(props.params.layout ? [props.params.layout] : EXPO_LAYOUTS)) ) - const getTitle = (l: string) => { - switch (l) { - case 'audio': { - return t('Audio') - } - case 'video': { - return t('Video') - } - case 'image': { - return t('Artworks') - } - case 'literature': { - return t('Literature') - } - default: { - return t('Art') - } + // Функция для загрузки дополнительных шотов + const loadMore = async () => { + saveScrollPosition() + const limit = SHOUTS_PER_PAGE + const layouts = props.params.layout ? [props.params.layout] : EXPO_LAYOUTS + const offset = expoFeed()?.length || 0 + const filters: LoadShoutsFilters = { layouts, featured: true } + const options: LoadShoutsOptions = { filters, limit, offset } + const shoutsFetcher = loadShouts(options) + const result = await shoutsFetcher() + setLoadMoreVisible(Boolean(result?.length)) + if (result) { + setExpoFeed((prev) => Array.from(new Set([...(prev || []), ...result])).sort(byCreated)) } + restoreScrollPosition() + return result as LoadMoreItems } - - onMount(() => { - document.title = getTitle(props.params.layout || '') - }) - + // Эффект для загрузки данных при изменении layout + createEffect( + on( + () => props.params.layout as ExpoLayoutType, + async (layout?: ExpoLayoutType) => { + const layouts = layout ? [layout] : EXPO_LAYOUTS + const offset = (layout ? feedByLayout()[layout]?.length : expoFeed()?.length) || 0 + const options: LoadShoutsOptions = { + filters: { layouts, featured: true }, + limit: SHOUTS_PER_PAGE, + offset + } + const shoutsFetcher = loadShouts(options) + const result = await shoutsFetcher() + setExpoFeed(result || []) + } + ) + ) return ( ) => { title={`${t('Discours')} :: ${getTitle(props.params.layout || '')}`} > - - {(sss) => } - + + ) } diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 8e0f882b..2911baf2 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -4,7 +4,8 @@ export type RootSearchParams = { token: string; }; -export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'; +export type ExpoLayoutType = 'audio' | 'video' | 'image' | 'literature'; +export type LayoutType = 'article' | ExpoLayoutType; export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities'; export type SortFunction = (a: T, b: T) => number export type FilterFunction = (a: T) => boolean