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
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/_types/integrations.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ export interface NuxtDevtoolsInfo {
packagePath: string
isGlobalInstall: boolean
}

export interface InstallModuleReturn {
configOriginal: string
configGenerated: string
commands: string[]
processId: string
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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>
Loading