You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
4.2 KiB
159 lines
4.2 KiB
<template>
|
|
<StickyCourses />
|
|
|
|
<hr />
|
|
|
|
<div class="relative min-h-[300px]">
|
|
<Loading :visible="!isFullyLoaded" />
|
|
<div
|
|
v-if="isLoading && courses.length === 0"
|
|
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
|
>
|
|
<Skeleton height="16rem" />
|
|
<Skeleton
|
|
class="hidden md:block"
|
|
height="16rem"
|
|
/>
|
|
<Skeleton
|
|
class="hidden lg:block"
|
|
height="16rem"
|
|
/>
|
|
<Skeleton
|
|
class="hidden xl:block"
|
|
height="16rem"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-if="courses.length > 0"
|
|
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
|
>
|
|
<CourseCardList :courses="courses" />
|
|
<div ref="lastCourseRef"></div>
|
|
</div>
|
|
<EmptyState
|
|
v-else-if="!isLoading && courses.length === 0"
|
|
:detail="t('Go to Explore to find a topic of interest, or wait for someone to subscribe you')"
|
|
:summary="t('You don\'t have any course yet.')"
|
|
icon="courses"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { nextTick, onMounted, ref, watch } from "vue"
|
|
import { useQuery } from "@vue/apollo-composable"
|
|
import { useI18n } from "vue-i18n"
|
|
import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js"
|
|
import Skeleton from "primevue/skeleton"
|
|
import StickyCourses from "../../../views/user/courses/StickyCourses.vue"
|
|
import CourseCardList from "../../../components/course/CourseCardList.vue"
|
|
import EmptyState from "../../../components/EmptyState"
|
|
import { useSecurityStore } from "../../../store/securityStore"
|
|
import Loading from "../../../components/Loading.vue"
|
|
|
|
const securityStore = useSecurityStore()
|
|
const { t } = useI18n()
|
|
|
|
const courses = ref([])
|
|
const isLoading = ref(false)
|
|
const isFullyLoaded = ref(false)
|
|
const endCursor = ref(null)
|
|
const hasMore = ref(true)
|
|
const lastCourseRef = ref(null)
|
|
|
|
const { result, fetchMore } = useQuery(GET_COURSE_REL_USER, {
|
|
user: securityStore.user["@id"],
|
|
first: 30,
|
|
after: null,
|
|
})
|
|
|
|
watch(result, (newResult) => {
|
|
if (newResult?.courseRelUsers) {
|
|
const newCourses = newResult.courseRelUsers.edges.map(({ node }) => node.course)
|
|
|
|
const filteredCourses = newCourses.filter(
|
|
(newCourse) => !courses.value.some((existingCourse) => existingCourse._id === newCourse._id),
|
|
)
|
|
|
|
courses.value.push(...filteredCourses)
|
|
endCursor.value = newResult.courseRelUsers.pageInfo.endCursor
|
|
hasMore.value = newResult.courseRelUsers.pageInfo.hasNextPage
|
|
|
|
nextTick(() => {
|
|
if (lastCourseRef.value) {
|
|
observer.observe(lastCourseRef.value)
|
|
}
|
|
})
|
|
}
|
|
isLoading.value = false
|
|
isFullyLoaded.value = true
|
|
})
|
|
|
|
const loadMoreCourses = () => {
|
|
if (!hasMore.value || isLoading.value) return
|
|
isLoading.value = true
|
|
|
|
fetchMore({
|
|
variables: {
|
|
user: securityStore.user["@id"],
|
|
first: 10,
|
|
after: endCursor.value,
|
|
},
|
|
updateQuery: (previousResult, { fetchMoreResult }) => {
|
|
if (!fetchMoreResult) return previousResult
|
|
|
|
const newCourses = fetchMoreResult.courseRelUsers.edges.map(({ node }) => node.course)
|
|
const filteredCourses = newCourses.filter(
|
|
(newCourse) => !courses.value.some((existingCourse) => existingCourse._id === newCourse._id),
|
|
)
|
|
courses.value.push(...filteredCourses)
|
|
endCursor.value = fetchMoreResult.courseRelUsers.pageInfo.endCursor
|
|
hasMore.value = fetchMoreResult.courseRelUsers.pageInfo.hasNextPage
|
|
|
|
return {
|
|
...previousResult,
|
|
courseRelUsers: {
|
|
...fetchMoreResult.courseRelUsers,
|
|
edges: [...previousResult.courseRelUsers.edges, ...fetchMoreResult.courseRelUsers.edges],
|
|
},
|
|
}
|
|
},
|
|
}).finally(() => {
|
|
isLoading.value = false
|
|
})
|
|
}
|
|
|
|
let observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (entries[0].isIntersecting) {
|
|
loadMoreCourses()
|
|
}
|
|
},
|
|
{
|
|
rootMargin: "300px",
|
|
},
|
|
)
|
|
|
|
onMounted(() => {
|
|
courses.value = []
|
|
endCursor.value = null
|
|
hasMore.value = true
|
|
isLoading.value = false
|
|
isFullyLoaded.value = false
|
|
|
|
if (observer) observer.disconnect()
|
|
observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (entries[0].isIntersecting) {
|
|
loadMoreCourses()
|
|
}
|
|
},
|
|
{
|
|
rootMargin: "300px",
|
|
},
|
|
)
|
|
|
|
loadMoreCourses()
|
|
})
|
|
</script>
|
|
|