Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions app/components/module/ModuleItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,27 @@ const props = withDefaults(
showBadge?: boolean
isAdded: boolean
showAddButton?: boolean
sortKey?: string
}>(),
{
showBadge: true,
showAddButton: true
showAddButton: true,
sortKey: 'downloads'
}
)

const { copy } = useClipboard()
const { selectedSort } = useModules()
const { track } = useAnalytics()

const publishedAgo = useTimeAgo(() => props.module.stats.publishedAt)
const createdAgo = useTimeAgo(() => props.module.stats.createdAt)

const relativeDate = computed(() =>
selectedSort.value.key === 'publishedAt' ? publishedAgo.value : createdAgo.value
props.sortKey === 'publishedAt' ? publishedAgo.value : createdAgo.value
)

const staticModuleDate = computed(() =>
selectedSort.value.key === 'publishedAt'
props.sortKey === 'publishedAt'
? formatDateByLocale('en', props.module.stats.publishedAt)
: formatDateByLocale('en', props.module.stats.createdAt)
)
Expand Down Expand Up @@ -181,7 +182,7 @@ const items = computed(() => [
</UTooltip>
</template>

<UTooltip v-if="selectedSort.key === 'publishedAt'" :text="`Updated ${formatDateByLocale('en', module.stats.publishedAt)}`">
<UTooltip v-if="sortKey === 'publishedAt'" :text="`Updated ${formatDateByLocale('en', module.stats.publishedAt)}`">
<NuxtLink
class="flex items-center gap-1 hover:text-highlighted"
:to="`https://github.com/${module.repo}`"
Expand All @@ -199,7 +200,7 @@ const items = computed(() => [
</NuxtLink>
</UTooltip>

<UTooltip v-if="selectedSort.key === 'createdAt'" :text="`Created ${formatDateByLocale('en', module.stats.createdAt)}`">
<UTooltip v-if="sortKey === 'createdAt'" :text="`Created ${formatDateByLocale('en', module.stats.createdAt)}`">
<NuxtLink
class="flex items-center gap-1 hover:text-highlighted"
:to="`https://github.com/${module.repo}`"
Expand Down
6 changes: 5 additions & 1 deletion app/composables/useModuleHealth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export const useModuleHealth = () => {
key: 'modules-health',
server: false,
lazy: true,
default: () => ({})
dedupe: 'defer',
default: () => ({}),
getCachedData(key, nuxtApp) {
return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key]
}
})

return { health }
Expand Down
8 changes: 1 addition & 7 deletions app/composables/useModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,7 @@ export const useModules = () => {
})

const stats = computed(() => data.value.stats)

// Merge via computed, not mutation: useFetch `data` is a shallowRef (Nuxt 4
// default), so writing module.health in place would not trigger a re-render.
const { health } = useModuleHealth()
const modules = computed<Module[]>(() =>
(data.value.modules || []).map(m => ({ ...m, health: health.value[m.name] ?? m.health ?? null }))
)
const modules = computed<Module[]>(() => data.value.modules || [])

const module = useState<Module>('module', () => ({} as Module))

Expand Down
63 changes: 42 additions & 21 deletions app/pages/modules/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ const el = useTemplateRef<HTMLElement>('el')

const { replaceRoute } = useFilters('modules')
const { fetchList, filteredModules, q, categories, modules, stats, selectedSort, selectedOrder, selectedCategory, sorts } = useModules()
const { health } = useModuleHealth()
const { track } = useAnalytics()

// Merge via computed, not mutation: useFetch `data` is a shallowRef (Nuxt 4
// default), so writing module.health in place would not trigger a re-render.
const filteredModulesWithHealth = computed(() =>
filteredModules.value.map(m => ({
...m,
health: health.value[m.name] ?? m.health ?? null
}))
)

const cacheControl = useResponseHeader('Cache-Control')
const cdnCacheControl = useResponseHeader('CDN-Cache-Control')

Expand Down Expand Up @@ -56,35 +66,39 @@ defineShortcuts({
})

const ITEMS_PER_PAGE = 9
const INITIAL_VISIBLE = ITEMS_PER_PAGE * 2
const SCROLL_THRESHOLD = 450
const displayedModules = ref<Module[]>([])
const visibleCount = ref(INITIAL_VISIBLE)
const isLoading = ref(false)
let loadMoreTimer: ReturnType<typeof setTimeout> | null = null

const displayedModules = computed(() =>
filteredModulesWithHealth.value.slice(0, visibleCount.value)
)

const { y: scrollY } = useWindowScroll()
const { copy } = useClipboard()

const clearLoadMoreTimer = () => {
if (loadMoreTimer === null) return
clearTimeout(loadMoreTimer)
loadMoreTimer = null
}

const loadMoreModules = () => {
if (isLoading.value) return

const currentLength = displayedModules.value.length
if (currentLength >= filteredModules.value.length) return
if (visibleCount.value >= filteredModules.value.length) return

isLoading.value = true
clearLoadMoreTimer()

setTimeout(() => {
const nextItems = filteredModules.value.slice(
currentLength,
currentLength + ITEMS_PER_PAGE
)
displayedModules.value.push(...nextItems)
loadMoreTimer = setTimeout(() => {
loadMoreTimer = null
visibleCount.value += ITEMS_PER_PAGE
isLoading.value = false
}, 300)
}

const initializeModules = () => {
displayedModules.value = filteredModules.value.slice(0, ITEMS_PER_PAGE * 2)
}

const debouncedLoadMore = useDebounceFn(loadMoreModules, 50)

watch(scrollY, (y) => {
Expand All @@ -93,11 +107,19 @@ watch(scrollY, (y) => {
}
})

watch(filteredModules, () => {
isLoading.value = false
displayedModules.value = []
initializeModules()
})
watch(
() => [
q.value,
selectedCategory.value?.key,
selectedSort.value?.key,
selectedOrder.value?.key
],
() => {
clearLoadMoreTimer()
isLoading.value = false
visibleCount.value = INITIAL_VISIBLE
}
)

const copyAllInstallCommands = () => {
const moduleNames = modulesToAdd.value.map(module => module.name).join(' ')
Expand Down Expand Up @@ -137,8 +159,6 @@ Steps:
const clearAllModules = () => {
modulesToAdd.value = []
}

initializeModules()
</script>

<template>
Expand Down Expand Up @@ -286,6 +306,7 @@ initializeModules()
v-for="module in displayedModules"
:key="module.name"
:module="module"
:sort-key="String(selectedSort.key)"
:is-added="modulesToAdd.some(m => m.name === module.name)"
@add="modulesToAdd.push(module)"
@remove="modulesToAdd = modulesToAdd.filter(m => m.name !== module.name)"
Expand Down