vue3-realworld-example-app/src/composable/useArticles.ts

142 lines
4.3 KiB
TypeScript

import type { AppRouteNames } from 'src/router'
import {
getArticles,
getArticlesByTag,
getFavoritedArticles,
getFeeds,
getProfileArticles,
} from 'src/services/article/getArticles'
import createAsyncProcess from 'src/utils/create-async-process'
import type { ComputedRef } from 'vue'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type
export function useArticles () {
const { articlesType, tag, username, metaChanged } = useArticlesMeta()
const articles = ref<Article[]>([])
const articlesCount = ref(0)
const page = ref(1)
async function fetchArticles (): Promise<void> {
articles.value = []
let responsePromise: null | Promise<ArticlesResponse> = null
if (articlesType.value === 'my-feed') {
responsePromise = getFeeds(page.value)
} else if (articlesType.value === 'tag-feed' && tag.value) {
responsePromise = getArticlesByTag(tag.value, page.value)
} else if (articlesType.value === 'user-feed' && username.value) {
responsePromise = getProfileArticles(username.value, page.value)
} else if (articlesType.value === 'user-favorites-feed' && username.value) {
responsePromise = getFavoritedArticles(username.value, page.value)
} else if (articlesType.value === 'global-feed') {
responsePromise = getArticles(page.value)
}
if (responsePromise !== null) {
const response = await responsePromise
articles.value = response.articles
articlesCount.value = response.articlesCount
} else {
throw new Error(`Articles type "${articlesType.value}" not supported`)
}
}
const changePage = (value: number): void => {
page.value = value
}
const updateArticle = (index: number, article: Article): void => {
articles.value[index] = article
}
const { active: articlesDownloading, run: runWrappedFetchArticles } = createAsyncProcess(fetchArticles)
watch(metaChanged, async () => {
if (page.value !== 1) changePage(1)
else await runWrappedFetchArticles()
})
watch(page, runWrappedFetchArticles)
return {
fetchArticles: runWrappedFetchArticles,
articlesDownloading,
articles,
articlesCount,
page,
changePage,
updateArticle,
tag,
username,
}
}
export type ArticlesType = 'global-feed' | 'my-feed' | 'tag-feed' | 'user-feed' | 'user-favorites-feed'
export const articlesTypes: ArticlesType[] = ['global-feed', 'my-feed', 'tag-feed', 'user-feed', 'user-favorites-feed']
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export const isArticlesType = (type: any): type is ArticlesType => articlesTypes.includes(type)
const routeNameToArticlesType: Partial<Record<AppRouteNames, ArticlesType>> = ({
'global-feed': 'global-feed',
'my-feed': 'my-feed',
tag: 'tag-feed',
profile: 'user-feed',
'profile-favorites': 'user-favorites-feed',
})
interface UseArticlesMetaReturn {
tag: ComputedRef<string>
username: ComputedRef<string>
articlesType: ComputedRef<ArticlesType>
metaChanged: ComputedRef<string>
}
function useArticlesMeta (): UseArticlesMetaReturn {
const route = useRoute()
const tag = ref('')
const username = ref('')
const articlesType = ref<ArticlesType>('global-feed')
watch(
() => route.name,
routeName => {
const possibleArticlesType = routeNameToArticlesType[routeName as AppRouteNames]
if (!isArticlesType(possibleArticlesType)) return
articlesType.value = possibleArticlesType
},
{ immediate: true },
)
watch(
() => route.params.username,
usernameParam => {
if (usernameParam !== username.value) {
username.value = typeof usernameParam === 'string' ? usernameParam : ''
}
},
{ immediate: true },
)
watch(
() => route.params.tag,
tagParam => {
if (tagParam !== tag.value) {
tag.value = typeof tagParam === 'string' ? tagParam : ''
}
},
{ immediate: true },
)
return {
tag: computed(() => tag.value),
username: computed(() => username.value),
articlesType: computed(() => articlesType.value),
metaChanged: computed(() => `${articlesType.value}-${username.value}-${tag.value}`),
}
}