Skip to content
This repository was archived by the owner on Mar 13, 2024. It is now read-only.

Commit 23fcd50

Browse files
feat: update npm (#12)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 005d39f commit 23fcd50

File tree

16 files changed

+533
-223
lines changed

16 files changed

+533
-223
lines changed

.github/assets/banner.png

2.57 MB
Loading

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
![Every UnJS Packages](./.github/assets/banner.png)
2+
13
# Relations UnJS
24

35
[![Nuxt UI Pro](https://img.shields.io/badge/Made%20with-Nuxt%20UI%20Pro-00DC82?logo=nuxt.js&labelColor=020420)](https://ui.nuxt.com/pro) [![code style](https://antfu.me/badge-code-style.svg)](https://github.com/antfu/eslint-config)
46

57
Discover UnJS ecosystem from a new angle! 🔭
68

9+
- 🪄 Visualize dependencies from UnJS packages
10+
- 👶 Print children UnJS dependencies
11+
- 🎨 Add any npm package to the graph
12+
13+
[👉 Check it out](https://unjs-relations.barbapapazes.dev/)
14+
715
## Setup
816

917
Make sure to install the dependencies:

app.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ export default defineAppConfig({
44
gray: 'cool',
55
button: {
66
base: 'transition ease-in',
7+
color: {
8+
white: {
9+
solid: 'shadow-none',
10+
},
11+
},
12+
variant: {
13+
solid: 'shadow-none text-gray-900',
14+
},
15+
},
16+
input: {
17+
variant: {
18+
outline: 'shadow-none',
19+
},
720
},
821
select: {
922
color: {

app.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<template>
2-
<Head>
3-
<Link rel="icon" href="/favicon.svg" />
4-
</Head>
52
<main>
63
<NuxtPage />
74
</main>
5+
6+
<UNotifications />
87
</template>

components/Graph.vue

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,34 @@ import type { Settings } from '~/types/settings'
77
88
const props = defineProps<{
99
packages: Package[]
10-
selection: string[]
10+
selection: Package[]
1111
settings: Settings
1212
}>()
1313
1414
const emits = defineEmits<{
15-
'selectNode': [string]
15+
'selectNode': [Package]
1616
}>()
1717
18+
const selectionNames = computed<string[]>(() => {
19+
return props.selection.map((select) => {
20+
return select.name
21+
})
22+
})
23+
1824
const container = ref<HTMLElement>()
1925
2026
const data = computed<Data>(() => {
2127
/** Selection */
2228
const selectionNodes: Data['nodes'] = props.selection.map((select) => {
23-
const logo = props.packages.find((pkg) => {
24-
return pkg.name === select
25-
})?.title
26-
2729
return {
28-
id: select,
29-
label: select,
30-
image: `https://unjs.io/assets/logos/${logo}.svg`,
30+
id: select.name,
31+
label: select.name,
32+
brokenImage: 'https://api.iconify.design/simple-icons/npm.svg',
33+
image: select.external ? `https://api.iconify.design/logos/${select.name}-icon.svg` : `https://unjs.io/assets/logos/${select.title}.svg`,
3134
group: 'selection',
3235
}
3336
})
3437
35-
const selectionPackages = props.packages.filter((pkg) => {
36-
return props.selection.includes(pkg.name)
37-
})
38-
3938
// Use a condition to avoid unnecessary computation
4039
const selectionChildrenPackages: Package[] = []
4140
const selectionChildrenPackagesName: string[] = []
@@ -47,7 +46,7 @@ const data = computed<Data>(() => {
4746
if (props.settings.dependencies) {
4847
// Check if current package use any of the selected packages
4948
const hasUsedBy = pkg.dependencies.some((dep) => {
50-
return props.selection.includes(dep)
49+
return selectionNames.value.includes(dep)
5150
})
5251
5352
if (hasUsedBy)
@@ -57,7 +56,7 @@ const data = computed<Data>(() => {
5756
if (props.settings.devDependencies) {
5857
// Check if current package use any of the selected packages
5958
const hasUsedBy = pkg.devDependencies.some((dep) => {
60-
return props.selection.includes(dep)
59+
return selectionNames.value.includes(dep)
6160
})
6261
6362
if (hasUsedBy)
@@ -71,10 +70,10 @@ const data = computed<Data>(() => {
7170
return {
7271
...pkg,
7372
dependencies: pkg.dependencies.filter((dep) => {
74-
return props.selection.includes(dep)
73+
return selectionNames.value.includes(dep)
7574
}),
7675
devDependencies: pkg.devDependencies.filter((dep) => {
77-
return props.selection.includes(dep)
76+
return selectionNames.value.includes(dep)
7877
}),
7978
}
8079
}))
@@ -85,7 +84,7 @@ const data = computed<Data>(() => {
8584
}
8685
8786
/** Dependencies and Dev Dependencies */
88-
const allDependencies = [...selectionPackages, ...selectionChildrenPackages].flatMap((pkg) => {
87+
const allDependencies = [...props.selection, ...selectionChildrenPackages].flatMap((pkg) => {
8988
const deps = []
9089
9190
// Add current package for selection children packages
@@ -105,7 +104,7 @@ const data = computed<Data>(() => {
105104
106105
// Remove selected packages from all dependencies since they are already in selection
107106
const dedupedWithoutSelectionAllDependencies = dedupedAllDependencies.filter((dep) => {
108-
return !props.selection.includes(dep)
107+
return !selectionNames.value.includes(dep)
109108
})
110109
111110
const allDependenciesNodes: Data['nodes'] = dedupedWithoutSelectionAllDependencies.flatMap((dep) => {
@@ -127,7 +126,7 @@ const data = computed<Data>(() => {
127126
]
128127
129128
// Order matters since we want to show the dependencies and devDependencies of the selected packages first (otherwise, some packages will not have all their dependencies shown)
130-
const dedupePackages = [...selectionPackages, ...selectionChildrenPackages].reduce((acc, pkg) => {
129+
const dedupePackages = [...props.selection, ...selectionChildrenPackages].reduce((acc, pkg) => {
131130
const index = acc.findIndex((p) => {
132131
return p.name === pkg.name
133132
})
@@ -237,7 +236,10 @@ onMounted(() => {
237236
const network = new Network(container.value!, data.value, options)
238237
239238
network.on('doubleClick', ({ nodes }) => {
240-
emits('selectNode', nodes[0])
239+
const package_ = [...props.packages, ...props.selection].find((pkg) => {
240+
return pkg.name === nodes[0]
241+
}) as Package
242+
emits('selectNode', package_)
241243
})
242244
243245
watch(data, () => {

components/GraphLegend.vue

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1+
<script lang="ts" setup>
2+
const legend = [{
3+
color: 'bg-yellow-500 ring-yellow-600',
4+
text: 'Selected packages',
5+
}, {
6+
color: 'bg-cyan-300 ring-cyan-500',
7+
text: 'UnJS Packages',
8+
}, {
9+
color: 'bg-pink-300 ring-pink-500',
10+
text: 'Used as dependencies',
11+
}, {
12+
color: 'bg-purple-300 ring-purple-500',
13+
text: 'Used as devDependencies',
14+
}]
15+
</script>
16+
117
<template>
2-
<div class="flex items-center gap-1">
3-
<span class="block rounded-full bg-yellow-500 w-2 h-2" />
4-
<span> Selected packages</span>
5-
</div>
6-
<div class="flex items-center gap-1">
7-
<span class="block rounded-full bg-cyan-300 w-2 h-2" />
8-
<span> UnJS Packages </span>
9-
</div>
10-
<div class="flex items-center gap-1">
11-
<span class="block rounded-full bg-pink-300 w-2 h-2" />
12-
<span> Used as dependencies </span>
13-
</div>
14-
<div class="flex items-center gap-1">
15-
<span class="block rounded-full bg-purple-300 w-2 h-2" />
16-
<span> Used as devDependencies </span>
17-
</div>
18+
<template v-for="item in legend" :key="item.text">
19+
<div class="flex items-center gap-2">
20+
<span class="block rounded-full w-3 h-3 ring-1" :class="item.color" />
21+
<span> {{ item.text }}</span>
22+
</div>
23+
</template>
1824
</template>

components/NpmPackageLogo.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts" setup>
2+
defineProps<{
3+
name: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<UAvatar icon="i-simple-icons-npm" :src="`https://api.iconify.design/logos/${name}-icon.svg`" aria-hidden="true" size="xs" :ui="{ rounded: '', icon: { base: 'text-inherit dark:text-inherit', size: { xs: 'w-6 h-6' } } }" />
9+
</template>

components/NpmPackagesModal.vue

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<script lang="ts" setup>
2+
import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/vue'
3+
import { UInput } from '#components'
4+
import type { Package } from '~/types/packages'
5+
6+
const props = defineProps<{
7+
unjsPackages: Package[]
8+
modelValue: boolean
9+
}>()
10+
11+
const emits = defineEmits<{
12+
'update:modelValue': [boolean]
13+
'selection': [Package[]]
14+
}>()
15+
16+
function close() {
17+
emits('update:modelValue', false)
18+
}
19+
20+
const packages = ref<Package[]>([])
21+
const selection = ref<Package[]>([])
22+
23+
const toast = useToast()
24+
const input = ref<string>('')
25+
const loading = ref<boolean>(false)
26+
// search for a package on npm using https://registry.npmjs.org/-/v1/search?text=nuxt
27+
async function addPackage() {
28+
const packageName = input.value.trim()
29+
30+
loading.value = true
31+
const { data, error } = await fetchPackage(packageName)
32+
loading.value = false
33+
34+
if (error.value) {
35+
if (error.value?.statusCode === 404) {
36+
toast.add({
37+
title: `'${packageName}' not found`,
38+
color: 'red',
39+
timeout: 3000,
40+
})
41+
}
42+
else {
43+
toast.add({
44+
title: 'Error',
45+
description: error.value?.message,
46+
color: 'red',
47+
timeout: 3000,
48+
})
49+
}
50+
}
51+
52+
if (data.value && packages.value.findIndex(p => p.name === data.value!.name) === -1) {
53+
const newPackage = toPackage(data.value, props.unjsPackages)
54+
packages.value.push(newPackage)
55+
selection.value.push(newPackage)
56+
input.value = ''
57+
}
58+
else {
59+
toast.add({
60+
title: `'${packageName}' already added`,
61+
color: 'orange',
62+
timeout: 3000,
63+
})
64+
}
65+
}
66+
67+
const query = ref<string>('')
68+
const search = computed(() => {
69+
return packages.value.filter(pkg => pkg.name.includes(query.value))
70+
})
71+
72+
function removePackage(package_: Package) {
73+
packages.value = packages.value.filter(pkg => pkg.name !== package_.name)
74+
selection.value = selection.value.filter(pkg => pkg.name !== package_.name)
75+
}
76+
77+
function clear() {
78+
selection.value = []
79+
}
80+
81+
function validate() {
82+
emits('selection', selection.value)
83+
close()
84+
}
85+
</script>
86+
87+
<template>
88+
<UModal :model-value="modelValue" :ui="{ width: 'xl:max-w-3xl' }" @update:model-value="close">
89+
<UCard>
90+
<template #header>
91+
<div class="flex justify-between">
92+
<h2 class="font-semibold">
93+
Manage npm Packages
94+
</h2>
95+
<UButton type="button" aria-label="close" color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="close" />
96+
</div>
97+
</template>
98+
99+
<section>
100+
<h3 class="text-sm">
101+
Add a package from npm
102+
</h3>
103+
104+
<form class="mt-1 flex gap-2 items-end" @submit.prevent="addPackage">
105+
<UInput v-model="input" color="primary" variant="outline" placeholder="nuxt, tailwindcss, ..." :ui="{ wrapper: 'grow' }" />
106+
<UButton color="primary" :disabled="!input" size="sm" type="submit" :loading="loading">
107+
Add
108+
</UButton>
109+
</form>
110+
</section>
111+
112+
<UDivider class="my-4" />
113+
114+
<Combobox v-model="selection" multiple as="template">
115+
<ComboboxInput v-model="query" :as="UInput" color="primary" variant="outline" placeholder="Search a package..." class="mb-2" />
116+
<ComboboxOptions static as="ol" class="grid xl:grid-cols-3 gap-2">
117+
<div v-if="!search.length && !selection.length" class="text-center text-sm xl:col-span-3">
118+
<span>
119+
Start by adding a package
120+
</span>
121+
</div>
122+
<template v-else-if="search.length">
123+
<li v-for="item in search" :key="item.name" class="w-full flex flex-row gap-1">
124+
<ComboboxOption v-slot="{ active, selected }" as="template" :value="item">
125+
<UButton :ui="{ base: 'grow' }" :class="{ 'text-gray-900 dark:text-white bg-gray-50 dark:bg-gray-800': active }" color="gray" variant="ghost" :active="active" tabindex="-1">
126+
<template #leading>
127+
<NpmPackageLogo :name="item.name" />
128+
</template>
129+
<span class="grow text-start">
130+
{{ item.name }}
131+
</span>
132+
<template v-if="selected" #trailing>
133+
<span class="i-ph-check" />
134+
</template>
135+
</UButton>
136+
</ComboboxOption>
137+
<UTooltip text="Delete">
138+
<UButton color="red" variant="ghost" type="button" icon="i-ph-trash" @click="removePackage(item)" />
139+
</UTooltip>
140+
</li>
141+
</template>
142+
143+
<div v-else class="text-center text-sm xl:col-span-3">
144+
<span>
145+
No results
146+
</span>
147+
</div>
148+
</ComboboxOptions>
149+
</Combobox>
150+
151+
<template #footer>
152+
<div class="flex justify-end gap-2">
153+
<UButton type="button" variant="ghost" color="red" @click="clear">
154+
Clear
155+
</UButton>
156+
<UButton type="button" @click="validate">
157+
Validate
158+
</UButton>
159+
</div>
160+
</template>
161+
</UCard>
162+
</UModal>
163+
</template>

0 commit comments

Comments
 (0)