Skip to content

Commit ab8083b

Browse files
committed
fix: improve module installing experience
1 parent 486db15 commit ab8083b

File tree

8 files changed

+184
-295
lines changed

8 files changed

+184
-295
lines changed

packages/devtools/client/components/ModuleActionDialog.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { ModuleDialog } from '../composables/state'
33
44
const config = useServerConfig()
55
const openInEditor = useOpenInEditor()
6+
7+
const anyObj = {} as any
68
</script>
79

810
<template>
911
<ModuleDialog v-slot="{ resolve, args }">
1012
<NDialog :model-value="true" @close="resolve(false)">
11-
<ModuleItemBase :mod="{}" :info="args[0]" border="none" w-150 n-panel-grids />
13+
<ModuleItemBase :mod="anyObj" :info="args[0]" border="none" w-150 n-panel-grids />
1214
<div flex="~ col gap-2" w-150 p4 border="t base">
1315
<h2 text-xl :class="args[2] === 'install' ? 'text-primary' : 'text-red'">
1416
<span capitalize>{{ args[2] }}</span> <code>{{ args[0].name }}</code>?

packages/devtools/client/components/ModuleInstallList.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { RecycleScroller } from 'vue-virtual-scroller'
44
import Fuse from 'fuse.js'
55
6+
const emit = defineEmits(['close'])
7+
68
const collection = useModulesList()
79
810
const search = ref('')
@@ -46,7 +48,7 @@ const items = computed(() => {
4648
:item-size="160"
4749
key-field="name"
4850
>
49-
<ModuleItemInstall :item="item" />
51+
<ModuleItemInstall :item="item" @start="emit('close')" />
5052
</RecycleScroller>
5153
</div>
5254
</div>

packages/devtools/client/components/ModuleItemBase.vue

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
<script setup lang="ts">
22
import type { InstalledModuleInfo, ModuleStaticInfo } from '../../src/types'
33
4-
const props = defineProps<{
5-
mod: InstalledModuleInfo
6-
info?: ModuleStaticInfo
7-
compact?: boolean
8-
}>()
4+
const props = withDefaults(
5+
defineProps<{
6+
mod: InstalledModuleInfo
7+
info?: ModuleStaticInfo
8+
compact?: boolean
9+
maintainers?: boolean
10+
}>(), {
11+
maintainers: true,
12+
},
13+
)
914
1015
const data = computed(() => ({
1116
...props.mod?.meta,
@@ -24,61 +29,63 @@ const openInEditor = useOpenInEditor()
2429
<template>
2530
<NCard p4 flex="~ gap2">
2631
<div flex="~ col gap2" flex-auto of-hidden px1>
27-
<div gap-1t flex items-center text-ellipsis ws-nowrap text-lg>
28-
<NuxtLink
29-
v-if="mod.isPackageModule"
30-
:to="npmBase + (data.npm || data.name)"
31-
target="_blank"
32-
hover="underline text-primary"
33-
>
34-
{{ data.name }}
35-
</NuxtLink>
36-
<button
37-
v-else-if="mod.entryPath"
38-
role="button"
39-
hover="underline text-primary"
40-
@click="openInEditor(mod.entryPath!)"
41-
>
42-
{{ data.name }}
43-
</button>
44-
<span v-else>
45-
{{ data.name }}
46-
</span>
47-
<slot name="badge" />
48-
</div>
32+
<slot name="main">
33+
<div gap-1t flex items-center text-ellipsis ws-nowrap text-lg>
34+
<NuxtLink
35+
v-if="mod.isPackageModule"
36+
:to="npmBase + (data.npm || data.name)"
37+
target="_blank"
38+
hover="underline text-primary"
39+
>
40+
{{ data.name }}
41+
</NuxtLink>
42+
<button
43+
v-else-if="mod.entryPath"
44+
role="button"
45+
hover="underline text-primary"
46+
@click="openInEditor(mod.entryPath!)"
47+
>
48+
{{ data.name }}
49+
</button>
50+
<span v-else>
51+
{{ data.name }}
52+
</span>
53+
<slot name="badge" />
54+
</div>
4955

50-
<div
51-
v-if="data.description "
52-
:class="compact ? 'ws-nowrap of-hidden truncate' : 'line-clamp-2'"
53-
mt--1 text-sm op50
54-
>
55-
{{ data.description }}
56-
</div>
56+
<div
57+
v-if="data.description "
58+
:class="compact ? 'ws-nowrap of-hidden truncate' : 'line-clamp-2'"
59+
mt--1 text-sm op50
60+
>
61+
{{ data.description }}
62+
</div>
5763

58-
<div flex-auto />
64+
<div flex-auto />
5965

60-
<div v-if="data.website" flex="~ gap-2" title="Documentation">
61-
<span i-carbon-link text-lg op50 />
62-
<NuxtLink
63-
:to="data.website"
64-
target="_blank"
65-
of-hidden truncate ws-nowrap text-sm op50
66-
hover="op100 underline text-primary"
67-
>
68-
{{ data.website.replace(/^https?:\/\//, '') }}
69-
</NuxtLink>
70-
</div>
71-
<div v-if="data.github" flex="~ gap-2">
72-
<span i-carbon-logo-github text-lg op50 />
73-
<NuxtLink
74-
:to="data.github"
75-
target="_blank"
76-
of-hidden truncate ws-nowrap text-sm op50
77-
hover="op100 underline text-primary"
78-
>
79-
{{ data.github.replace(/^https?:\/\/github.com\//, '') }}
80-
</NuxtLink>
81-
</div>
66+
<div v-if="data.website" flex="~ gap-2" title="Documentation">
67+
<span i-carbon-link text-lg op50 />
68+
<NuxtLink
69+
:to="data.website"
70+
target="_blank"
71+
of-hidden truncate ws-nowrap text-sm op50
72+
hover="op100 underline text-primary"
73+
>
74+
{{ data.website.replace(/^https?:\/\//, '') }}
75+
</NuxtLink>
76+
</div>
77+
<div v-if="data.github" flex="~ gap-2">
78+
<span i-carbon-logo-github text-lg op50 />
79+
<NuxtLink
80+
:to="data.github"
81+
target="_blank"
82+
of-hidden truncate ws-nowrap text-sm op50
83+
hover="op100 underline text-primary"
84+
>
85+
{{ data.github.replace(/^https?:\/\/github.com\//, '') }}
86+
</NuxtLink>
87+
</div>
88+
</slot>
8289

8390
<slot name="items" />
8491
</div>
@@ -89,9 +96,9 @@ const openInEditor = useOpenInEditor()
8996
h-20 w-20 flex flex-none rounded bg-gray:3 p4
9097
>
9198
<img v-if="data.icon" :src="iconBase + data.icon" :alt="mod.name" ma>
92-
<div i-carbon-circle-dash ma text-4xl op50 />
99+
<div i-carbon-cube ma text-4xl op30 />
93100
</div>
94-
<div v-if="data.maintainers?.length" flex="~" mt2 flex-auto items-end justify-end>
101+
<div v-if="data.maintainers?.length && maintainers" flex="~" mt2 flex-auto items-end justify-end>
95102
<NuxtLink
96103
v-for="m of data.maintainers"
97104
:key="m.name"

packages/devtools/client/components/ModuleItemInstall.vue

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,45 @@
11
<script setup lang="ts">
22
import type { ModuleStaticInfo } from '../../src/types'
3+
import type { ModuleActionType } from '~/composables/state'
34
45
const props = defineProps<{
56
item: ModuleStaticInfo
67
}>()
78
9+
const emit = defineEmits(['start'])
10+
811
const installedModules = useInstalledModules()
912
const installedInfo = computed(() => installedModules.value.find(i => i.name === props.item.npm))
1013
const isInstalled = computed(() => installedInfo.value && installedInfo.value.isPackageModule)
1114
const isUninstallable = computed(() => installedInfo.value && installedInfo.value.isPackageModule && installedInfo.value.isUninstallable)
15+
16+
async function useModuleAction(item: ModuleStaticInfo, type: ModuleActionType) {
17+
const method = type === 'install' ? rpc.installNuxtModule : rpc.uninstallNuxtModule
18+
const result = await method(item.npm, true)
19+
20+
if (!result.commands)
21+
return
22+
23+
if (!await ModuleDialog.start(item, result, type))
24+
return
25+
26+
installingModules.value.push({
27+
name: item.npm,
28+
info: item,
29+
processId: result.processId,
30+
})
31+
32+
emit('start')
33+
34+
await method(item.npm, false)
35+
}
36+
37+
const anyObj = {} as any
1238
</script>
1339

1440
<template>
1541
<ModuleItemBase
16-
:mod="{}"
42+
:mod="anyObj"
1743
:role="isInstalled ? '' : 'button'"
1844
:info="item"
1945
mb2 h-full

packages/devtools/client/composables/state.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ const ignoredModules = [
1515
'@nuxt/telemetry',
1616
]
1717

18+
export interface InstallingModuleState {
19+
name: string
20+
info: ModuleStaticInfo
21+
processId: string
22+
}
23+
1824
export function useModulesList() {
1925
return useAsyncData('modules-list', async () => {
2026
const modules = await $fetch<ModuleStaticInfo[]>('https://cdn.jsdelivr.net/npm/@nuxt/modules@latest/modules.json')
@@ -23,6 +29,8 @@ export function useModulesList() {
2329
}).data
2430
}
2531

32+
export const installingModules = ref<InstallingModuleState[]>([])
33+
2634
export function useInstalledModules() {
2735
return useState('installed-modules', () => {
2836
const config = useServerConfig()
@@ -61,25 +69,10 @@ export function useInstalledModules() {
6169
})
6270
}
6371

64-
type ModuleActionType = 'install' | 'uninstall'
72+
export type ModuleActionType = 'install' | 'uninstall'
6573

6674
export const ModuleDialog = createTemplatePromise<boolean, [info: ModuleStaticInfo, result: InstallModuleReturn, type: ModuleActionType]>()
6775

68-
export async function useModuleAction(item: ModuleStaticInfo, type: ModuleActionType) {
69-
const router = useRouter()
70-
const method = type === 'install' ? rpc.installNuxtModule : rpc.uninstallNuxtModule
71-
const result = await method(item.npm, true)
72-
73-
if (!result.commands)
74-
return
75-
76-
if (!await ModuleDialog.start(item, result, type))
77-
return
78-
79-
router.push(`/modules/terminals?id=${encodeURIComponent(result.processId)}`)
80-
await method(item.npm, false)
81-
}
82-
8376
export function useComponents() {
8477
const client = useClient()
8578
const serverComponents = useAsyncState('getComponents', () => rpc.getComponents())

packages/devtools/client/pages/modules/modules.vue

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const installedModules = useInstalledModules()
1010
1111
const packageModules = computed(() => installedModules.value.filter(i => i.isPackageModule))
1212
const userModules = computed(() => installedModules.value.filter(i => !i.isPackageModule))
13+
14+
const iconBase = 'https://api.nuxtjs.org/api/ipx/s_80,f_webp/gh/nuxt/modules/main/icons/'
1315
</script>
1416

1517
<template>
@@ -26,6 +28,29 @@ const userModules = computed(() => installedModules.value.filter(i => !i.isPacka
2628
:key="m.name"
2729
:mod="m"
2830
/>
31+
<NuxtLink
32+
v-for="m of installingModules"
33+
:key="m.processId" block min-h-30
34+
:to="`/modules/terminals?id=${encodeURIComponent(m.processId)}`"
35+
>
36+
<NCard
37+
border="1.5 dashed"
38+
h-full animate-pulse p4 transition
39+
hover="border-primary"
40+
flex="~ col gap-1 items-center justify-center"
41+
role="button"
42+
class="group"
43+
>
44+
<div relative h-20 w-20 flex flex-none rounded bg-gray:3 p3>
45+
<img :src="iconBase + m.info.icon" :alt="m.info.name" ma>
46+
<div i-carbon-cube ma text-4xl op30 />
47+
</div>
48+
<div text-lg group-hover="text-primary" transition flex="~ gap-2 items-center">
49+
<div i-carbon-circle-dash animate-spin text-xl op75 />
50+
<span op75>Installing {{ m.name }}...</span>
51+
</div>
52+
</NCard>
53+
</NuxtLink>
2954
<NCard
3055
border="1.5 dashed"
3156
min-h-30 p4 transition
@@ -76,7 +101,7 @@ const userModules = computed(() => installedModules.value.filter(i => !i.isPacka
76101
@click="installModuleOpen = false"
77102
/>
78103

79-
<ModuleInstallList />
104+
<ModuleInstallList @close="installModuleOpen = false" />
80105
</div>
81106
</Transition>
82107
<ModuleActionDialog />

packages/devtools/client/setup/client-rpc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export function setupClientRPC() {
1919
async onTerminalExit(data) {
2020
// @ts-expect-error fail to extend hooks
2121
nuxt.hooks.callHookParallel('devtools:terminal:exit', data)
22+
23+
// remove installing modules entry
24+
const index = installingModules.value.findIndex(i => i.processId === data.id)
25+
if (index !== -1)
26+
installingModules.value.splice(index, 1)
2227
},
2328
async navigateTo(path: string) {
2429
if (router.currentRoute.value.path !== path)

0 commit comments

Comments
 (0)