forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2import type { PackageFileTree } from '#shared/types'
3import type { RouteLocationRaw } from 'vue-router'
4import type { RouteNamedMap } from 'vue-router/auto-routes'
5import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons'
6
7const props = defineProps<{
8 tree: PackageFileTree[]
9 currentPath: string
10 baseUrl: string
11 baseRoute: Pick<RouteNamedMap['code'], 'params'>
12}>()
13
14// Get the current directory's contents
15const currentContents = computed(() => {
16 if (!props.currentPath) {
17 return props.tree
18 }
19
20 const parts = props.currentPath.split('/')
21 let current: PackageFileTree[] | undefined = props.tree
22
23 for (const part of parts) {
24 const found: PackageFileTree | undefined = current?.find(n => n.name === part)
25 if (!found || found.type === 'file') {
26 return []
27 }
28 current = found.children
29 }
30
31 return current ?? []
32})
33
34// Get parent directory path
35const parentPath = computed(() => {
36 if (!props.currentPath) return null
37 const parts = props.currentPath.split('/')
38 if (parts.length <= 1) return ''
39 return parts.slice(0, -1).join('/')
40})
41
42// Build route object for a path
43function getCodeRoute(nodePath?: string): RouteLocationRaw {
44 return {
45 name: 'code',
46 params: {
47 org: props.baseRoute.params.org,
48 packageName: props.baseRoute.params.packageName,
49 version: props.baseRoute.params.version,
50 filePath: nodePath ?? '',
51 },
52 }
53}
54
55const bytesFormatter = useBytesFormatter()
56</script>
57
58<template>
59 <div class="directory-listing">
60 <!-- Empty state -->
61 <div v-if="currentContents.length === 0" class="py-20 text-center text-fg-muted">
62 <p>{{ $t('code.no_files') }}</p>
63 </div>
64
65 <!-- File list -->
66 <table v-else class="w-full">
67 <thead class="sr-only">
68 <tr>
69 <th>{{ $t('code.table.name') }}</th>
70 <th>{{ $t('code.table.size') }}</th>
71 </tr>
72 </thead>
73 <tbody>
74 <!-- Parent directory link -->
75 <tr
76 v-if="parentPath !== null"
77 class="border-b border-border hover:bg-bg-subtle transition-colors"
78 >
79 <td colspan="2">
80 <LinkBase
81 :to="getCodeRoute(parentPath || undefined)"
82 class="py-2 px-4 font-mono text-sm w-full"
83 no-underline
84 >
85 <svg
86 class="size-[1em] me-1 shrink-0 text-yellow-600"
87 viewBox="0 0 16 16"
88 fill="currentColor"
89 aria-hidden="true"
90 >
91 <use :href="`/file-tree-sprite.svg#${ADDITIONAL_ICONS['folder']}`" />
92 </svg>
93 <span class="w-full flex justify-self-stretch items-center gap-2"> .. </span>
94 </LinkBase>
95 </td>
96 </tr>
97
98 <!-- Directory/file rows -->
99 <tr
100 v-for="node in currentContents"
101 :key="node.path"
102 class="border-b border-border hover:bg-bg-subtle transition-colors"
103 >
104 <td colspan="2">
105 <LinkBase
106 :to="getCodeRoute(node.path)"
107 class="py-2 px-4 font-mono text-sm w-full"
108 no-underline
109 >
110 <svg
111 class="size-[1em] me-1 shrink-0"
112 viewBox="0 0 16 16"
113 fill="currentColor"
114 :class="node.type === 'directory' ? 'text-yellow-600' : undefined"
115 aria-hidden="true"
116 >
117 <use
118 :href="`/file-tree-sprite.svg#${node.type === 'directory' ? ADDITIONAL_ICONS['folder'] : getFileIcon(node.name)}`"
119 />
120 </svg>
121 <span class="w-full flex justify-self-stretch items-center gap-2">
122 <span class="flex-1">{{ node.name }}</span>
123 <span
124 v-if="node.type === 'file' && node.size"
125 class="text-end text-xs text-fg-subtle"
126 >
127 {{ bytesFormatter.format(node.size) }}
128 </span>
129 </span>
130 </LinkBase>
131 </td>
132 </tr>
133 </tbody>
134 </table>
135 </div>
136</template>