Skip to content

Commit

Permalink
feat: experimental add module from DevTools (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 10, 2023
1 parent 782e0da commit 501682b
Show file tree
Hide file tree
Showing 22 changed files with 1,124 additions and 281 deletions.
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/_types/integrations.ts
Expand Up @@ -81,7 +81,7 @@ export interface BasicModuleInfo {
}
}

export interface ModuleMetric {
export interface ModuleStaticInfo {
name: string
description: string
repo: string
Expand Down
2 changes: 2 additions & 0 deletions packages/devtools-kit/src/_types/rpc.ts
Expand Up @@ -5,6 +5,7 @@ import type { ModuleCustomTab } from './custom-tabs'
import type { AssetInfo, AutoImportsWithMetadata, ComponentRelationship, HookInfo, ImageMeta, NpmCommandOptions, NpmCommandType, PackageManagerName, PackageUpdateInfo, ServerRouteInfo } from './integrations'
import type { TerminalAction, TerminalInfo } from './terminals'
import type { GetWizardArgs, WizardActions } from './wizard'
import type { InstallModuleReturn } from './server-ctx'

export interface ServerFunctions {
// Static RPCs (can be provide on production build in the future)
Expand Down Expand Up @@ -48,6 +49,7 @@ export interface ServerFunctions {
runWizard<T extends WizardActions>(name: T, ...args: GetWizardArgs<T>): Promise<void>
openInEditor(filepath: string): Promise<boolean>
restartNuxt(hard?: boolean): Promise<void>
installNuxtModule(name: string, dry?: boolean): Promise<InstallModuleReturn>
}

export interface ClientFunctions {
Expand Down
7 changes: 7 additions & 0 deletions packages/devtools-kit/src/_types/server-ctx.ts
Expand Up @@ -30,3 +30,10 @@ export interface NuxtDevtoolsInfo {
packagePath: string
isGlobalInstall: boolean
}

export interface InstallModuleReturn {
configOriginal: string
configGenerated: string
commands: string[]
processId: string
}
Expand Up @@ -19,7 +19,7 @@ watch(show, async () => {

<template>
<div class="mt-2">
<span n="xs" class="n-link cursor-pointer hover:n-link-hover text-gray n-transition n-link-base" @click="show = !show">
<span n="xs" class="n-link cursor-pointer text-gray n-transition hover:n-link-hover n-link-base" @click="show = !show">
{{ show ? 'Hide' : 'Show' }} source
</span>
<div v-if="show" ref="embed" class="dark:filter-invert-100" />
Expand Down
56 changes: 56 additions & 0 deletions packages/devtools-ui-kit/src/assets/styles.css
Expand Up @@ -16,6 +16,62 @@ html.dark {
background: #8884;
}

/* Shiki */
.shiki .line {
position: relative;
display: inline-block;
width: 100%;
}

.shiki.diff .line > span {
opacity: 0.75;
filter: saturate(0.75);
}

.shiki.diff .line-added,
.shiki.diff .line-removed {
scroll-margin: 5em;
}

.shiki.diff .line-added > span,
.shiki.diff .line-removed > span {
opacity: 1 !important;
z-index: 100;
position: inherit;
scroll-margin: 20px;
}

.shiki.diff .line-added > span {
color: #218c3b !important;
}

.shiki.diff .line-removed > span {
color: #8c2c21 !important;
}

.shiki .line-added:after {
content: "";
background-color: #43885440;
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}

.shiki .line-removed:after {
content: "";
background-color: #8f4b3950;
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}

/* Color Mode transition */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
Expand Down
15 changes: 13 additions & 2 deletions packages/devtools-ui-kit/src/components/NCodeBlock.vue
@@ -1,18 +1,29 @@
<script setup lang="ts">
// This components requires to run in DevTools to render correctly
import { computed } from 'vue'
import { computed, nextTick } from 'vue'
import { devToolsClient } from '../runtime/client'
const props = withDefaults(
defineProps<{
code: string
lang?: string
lines?: boolean
transformRendered?: (code: string) => string
}>(), {
lines: true,
},
)
const rendered = computed(() => devToolsClient.value?.devtools.renderCodeHighlight(props.code, props.lang as string) || { code: props.code, supported: false })
const emit = defineEmits(['loaded'])
const rendered = computed(() => {
const result = devToolsClient.value?.devtools.renderCodeHighlight(props.code, props.lang as string) || { code: props.code, supported: false }
if (result.supported && props.transformRendered)
result.code = props.transformRendered(result.code)
if (result.supported)
nextTick(() => emit('loaded'))
return result
})
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-ui-kit/src/components/NDialog.vue
Expand Up @@ -59,7 +59,7 @@ export default {
]"
@click="close()"
/>
<NCard v-bind="$attrs" ref="card">
<NCard v-bind="$attrs" ref="card" class="max-h-screen of-auto">
<slot />
</NCard>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-ui-kit/src/components/NLink.vue
Expand Up @@ -14,7 +14,7 @@ defineProps<{
<Component
:is="(href || target) ? 'a' : NuxtLink"
v-bind="$props"
class="n-link hover:n-link-hover n-transition n-link-base"
class="n-link n-transition hover:n-link-hover n-link-base"
>
<slot />
</Component>
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-wizard/package.json
Expand Up @@ -24,7 +24,7 @@
"diff": "^5.1.0",
"execa": "^7.1.1",
"global-dirs": "^3.0.1",
"magicast": "^0.2.5",
"magicast": "^0.2.6",
"pathe": "^1.1.0",
"picocolors": "^1.0.0",
"pkg-types": "^1.0.3",
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/app.vue
Expand Up @@ -2,6 +2,7 @@
import 'floating-vue/dist/style.css'
import 'vanilla-jsoneditor/themes/jse-theme-dark.css'
import 'splitpanes/dist/splitpanes.css'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import './styles/global.css'
import { setupClientRPC } from './setup/client-rpc'
Expand Down
76 changes: 76 additions & 0 deletions packages/devtools/client/components/CodeDiff.vue
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { diffLines } from 'diff'
import { unrefElement } from '@vueuse/core'
const props = defineProps<{
from: string
to: string
lang: string
}>()
function calculateDiff(from: string, to: string) {
const diffs = diffLines(from.trim(), to.trim())
const added: number[] = []
const removed: number[] = []
const result = []
for (const diff of diffs) {
const lines = diff.value.trimEnd().split('\n')
for (const line of lines) {
if (diff.added) {
added.push(result.length)
result.push(line)
}
else if (diff.removed) {
removed.push(result.length)
result.push(line)
}
else {
result.push(line)
}
}
}
return {
added,
removed,
result: result.join('\n'),
}
}
const diff = computed(() => calculateDiff(props.from, props.to))
function transformRendered(code: string) {
let count = 0
return code
.replace(/class="shiki/, 'class="shiki diff')
.replace(/class="line"/g, (_) => {
count++
if (diff.value.added.includes(count - 1))
return 'class="line line-added"'
if (diff.value.removed.includes(count - 1))
return 'class="line line-removed"'
return _
})
}
const elRef = ref<HTMLDivElement>()
onMounted(scrollTo)
function scrollTo() {
const el = unrefElement(elRef)
if (el)
el.querySelector('.line-added,.line-removed')?.scrollIntoView()
}
</script>

<template>
<NCodeBlock
ref="elRef"
:code="diff.result"
:lang="lang"
:transform-rendered="transformRendered"
@loaded="scrollTo"
/>
</template>
124 changes: 124 additions & 0 deletions packages/devtools/client/components/ModuleInstallList.vue
@@ -0,0 +1,124 @@
<script setup lang="ts">
// @ts-expect-error missing types
import { RecycleScroller } from 'vue-virtual-scroller'
import type { InstallModuleReturn, ModuleStaticInfo } from '@nuxt/devtools-kit/types'
import Fuse from 'fuse.js'
const Dialog = createTemplatePromise<boolean, [info: ModuleStaticInfo, result: InstallModuleReturn]>()
const collection = await useModulesInfo()
const nuxt3only = collection.filter(i => i.compatibility.nuxt.includes('^3'))
const config = useServerConfig()
const router = useRouter()
const search = ref('')
const fuse = computed(() => new Fuse(nuxt3only, {
keys: [
'name',
'description',
'npm',
'category',
],
}))
const items = computed(() => {
if (!search.value)
return nuxt3only
return fuse.value.search(search.value).map(r => r.item)
})
async function install(item: ModuleStaticInfo) {
const result = await rpc.installNuxtModule(item.npm, true)
if (!result.commands)
return
if (!await Dialog.start(item, result))
return
router.push(`/modules/terminals?id=${encodeURIComponent(result.processId)}`)
await rpc.installNuxtModule(item.npm, false)
}
const openInEditor = useOpenInEditor()
</script>

<template>
<div h-full flex="~ col gap-4">
<NIconTitle
mx6 mt6
text-xl op75
icon="i-carbon-3d-mpr-toggle"
text="Install Module"
/>

<NTextInput
v-model="search"
placeholder="Search..."
icon="carbon-search" n="primary"
mx6 px-5 py-2
/>

<div flex-auto of-auto flex="~ col gap-2" pl6 pr4>
<RecycleScroller
v-slot="{ item }"
class="scroller"
:items="items"
:item-size="160"
key-field="name"
>
<ModuleItemBase
:mod="{}"
role="button"
:info="item"
mb2 h-full class="hover:bg-active!"
:compact="true"
@click="install(item)"
/>
</RecycleScroller>
</div>
</div>

<Dialog v-slot="{ resolve, args }">
<NDialog :model-value="true" @close="resolve(false)">
<ModuleItemBase :mod="{}" :info="args[0]" border="none" w-150 n-panel-grids />
<div flex="~ col gap-2" w-150 p4 border="t base">
<h2 text-xl>
Installing <span capitalize text-primary>{{ args[0].name }}</span> module?
</h2>

<p op50>
Following command will be executed in your terminal:
</p>
<NCodeBlock :code="args[1].commands.join(' ')" lang="bash" px4 py2 border="~ base rounded" :lines="false" />

<p op50>
Then your <NLink role="button" n="primary" @click="openInEditor(config?._nuxtConfigFile)" v-text="'Nuxt config'" /> will be updated as:
</p>
<CodeDiff
:from="args[1].configOriginal"
:to="args[1].configGenerated"
max-h-80 of-auto py2 border="~ base rounded"
lang="ts"
/>
<p>
<span op50>After module installed, Nuxt will </span><span text-orange>restart automatically</span>.
</p>
<div flex="~ gap-3" mt2 justify-end>
<NTip n="sm purple" flex-auto icon="carbon-chemistry">
Experimental. Make sure to backup your project.
</NTip>
<NButton @click="resolve(false)">
Cancel
</NButton>
<NButton n="solid primary" @click="resolve(true)">
Install
</NButton>
</div>
</div>
</NDialog>
</Dialog>
</template>

0 comments on commit 501682b

Please sign in to comment.