Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modules): dynamic sorting for module installation list #470

Merged
merged 1 commit into from
Oct 16, 2023
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
8 changes: 8 additions & 0 deletions packages/devtools-kit/src/_types/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface ModuleStaticInfo {
learn_more: string
category: string
type: ModuleType
stats: ModuleStats
maintainers: MaintainerInfo[]
contributors: GitHubContributor[]
compatibility: ModuleCompatibility
Expand All @@ -132,6 +133,13 @@ export interface ModuleCompatibility {
requires: { bridge?: boolean | 'optional' }
}

export interface ModuleStats {
downloads: number
stars: number
publishedAt: number
createdAt: number
}

export type CompatibilityStatus = 'working' | 'wip' | 'unknown' | 'not-working'
export type ModuleType = 'community' | 'official' | '3rd-party'

Expand Down
68 changes: 58 additions & 10 deletions packages/devtools/client/components/ModuleInstallList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@
// @ts-expect-error missing types
import { RecycleScroller } from 'vue-virtual-scroller'
import Fuse from 'fuse.js'
import type { ModuleStaticInfo } from '../../src/types'

type SortingFunction<T> = (a: T, b: T) => number

const emit = defineEmits(['close'])

const collection = useModulesList()

const sortingOptions = ['downloads', 'stars', 'updated', 'created'] as const
const ascendingOrder = ref(false)
const selectedSortingOption = ref<typeof sortingOptions[number]>(sortingOptions[0])

const sortingFactors: Record<typeof sortingOptions[number], SortingFunction<ModuleStaticInfo>> = {
downloads: (a, b) => a.stats.downloads - b.stats.downloads,
stars: (a, b) => a.stats.stars - b.stats.stars,
created: (a, b) => a.stats.createdAt - b.stats.createdAt,
updated: (a, b) => a.stats.publishedAt - b.stats.publishedAt,
}

const sortedItems = computed(() => collection.value?.slice()
.sort((a, b) => sortingFactors[selectedSortingOption.value](a, b) * (ascendingOrder.value ? 1 : -1)))

const search = ref('')
const fuse = computed(() => new Fuse(collection.value || [], {
const fuse = computed(() => new Fuse(sortedItems.value || [], {
keys: [
'name',
'description',
Expand All @@ -19,7 +36,7 @@ const fuse = computed(() => new Fuse(collection.value || [], {

const items = computed(() => {
if (!search.value)
return collection.value
return sortedItems.value
return fuse.value.search(search.value).map(r => r.item)
})
</script>
Expand All @@ -33,20 +50,51 @@ const items = computed(() => {
text="Install Module"
/>

<NTextInput
v-model="search"
:autofocus="true"
placeholder="Search..."
icon="carbon-search" n="primary"
mx6 px-5 py-2
/>
<NNavbar v-model:search="search" no-padding px-6 pb-5 pt-2>
<template #actions>
<NDropdown direction="end" n="sm primary">
<template #trigger="{ click }">
<div flex="~ items-center">
<NButton
:icon="ascendingOrder ? 'tabler:sort-ascending' : 'tabler:sort-descending'"
h-full rounded-r-none
@click="ascendingOrder = !ascendingOrder"
/>
<NButton
flex="~ justify-between"
min-w-30 border-l-0 rounded-l-none px-2 capitalize
hover="border-l-1"
@click="click()"
>
{{ selectedSortingOption }}
<NIcon icon="carbon:chevron-down" />
</NButton>
</div>
</template>
<div flex="~ col" w-30 of-auto>
<NButton
v-for="item of sortingOptions"
:key="item"
:border="false" p2
hover="n-checkbox-hover text-green"
@click="selectedSortingOption = item"
>
<span flex="~ justify-between" w-full text-xs capitalize op75>
{{ item }}
<NIcon v-if="selectedSortingOption === item" icon="carbon:checkmark" />
</span>
</NButton>
</div>
</NDropdown>
</template>
</NNavbar>

<div flex-auto of-auto flex="~ col gap-2" pl6 pr4>
<RecycleScroller
v-slot="{ item }"
class="scroller"
:items="items"
:item-size="160"
:item-size="200"
key-field="name"
>
<ModuleItemInstall :item="item" @start="emit('close')" />
Expand Down
15 changes: 15 additions & 0 deletions packages/devtools/client/components/ModuleItemBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ const openInEditor = useOpenInEditor()
</slot>

<slot name="items" />

<div v-if="data.stats" flex="~ gap-4 items-center">
<div flex="~ gap-1 items-center" op50>
<NIcon icon="carbon-star" text-lg />
<span>
{{ data.stats.stars }}
</span>
</div>
<div flex="~ gap-1 items-center" op50>
<NIcon icon="carbon-download" text-lg />
<span>
{{ data.stats.downloads }}
</span>
</div>
</div>
</div>
<div flex="~ col" items-end>
<div
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools/client/composables/state-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const ignoredModules = [

export function useModulesList() {
return useAsyncState('getModulesList', async () => {
const modules = await $fetch<ModuleStaticInfo[]>('https://cdn.jsdelivr.net/npm/@nuxt/modules@latest/modules.json')
return modules
.filter((m: ModuleStaticInfo) => !ignoredModules.includes(m.npm) && m.compatibility.nuxt.includes('^3'))
const m = await $fetch<{ modules: ModuleStaticInfo[] }>('https://api.nuxt.com/modules?version=3')
return m.modules
.filter((item: ModuleStaticInfo) => !ignoredModules.includes(item.npm) && item.compatibility.nuxt.includes('^3'))
})
}

Expand Down