Skip to content

Commit b9edee1

Browse files
authored
feat: add copy button to connector modal + respect pm preference (#76)
1 parent 983f5f6 commit b9edee1

4 files changed

Lines changed: 61 additions & 21 deletions

File tree

app/components/ConnectorModal.vue

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disco
66
77
const tokenInput = ref('')
88
const portInput = ref('31415')
9+
const copied = ref(false)
910
1011
async function handleConnect() {
1112
const port = Number.parseInt(portInput.value, 10) || 31415
@@ -20,6 +21,27 @@ function handleDisconnect() {
2021
disconnect()
2122
}
2223
24+
function copyCommand() {
25+
let command = executeNpmxConnectorCommand.value
26+
if (portInput.value !== '31415') {
27+
command += ` --port ${portInput.value}`
28+
}
29+
navigator.clipboard.writeText(command)
30+
copied.value = true
31+
setTimeout(() => {
32+
copied.value = false
33+
}, 2000)
34+
}
35+
36+
const selectedPM = useSelectedPackageManager()
37+
38+
const executeNpmxConnectorCommand = computed(() => {
39+
return getExecuteCommand({
40+
packageName: 'npmx-connector',
41+
packageManager: selectedPM.value,
42+
})
43+
})
44+
2345
// Reset form when modal opens
2446
watch(open, isOpen => {
2547
if (isOpen) {
@@ -103,9 +125,24 @@ watch(open, isOpen => {
103125
Run the connector on your machine to enable admin features:
104126
</p>
105127

106-
<div class="p-3 bg-[#0d0d0d] border border-border rounded-lg font-mono text-sm">
128+
<div
129+
class="flex items-center p-3 bg-[#0d0d0d] border border-border rounded-lg font-mono text-sm"
130+
>
107131
<span class="text-fg-subtle">$</span>
108-
<span class="text-fg ml-2">npx&nbsp;npmx-connector</span>
132+
<span class="text-fg ml-2">{{ executeNpmxConnectorCommand }}</span>
133+
<button
134+
type="button"
135+
:aria-label="copied ? 'Copied' : 'Copy command'"
136+
class="ml-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
137+
@click="copyCommand"
138+
>
139+
<span v-if="!copied" class="i-carbon-copy block w-5 h-5" aria-hidden="true" />
140+
<span
141+
v-else
142+
class="i-carbon-checkmark block w-5 h-5 text-green-500"
143+
aria-hidden="true"
144+
/>
145+
</button>
109146
</div>
110147

111148
<p class="text-sm text-fg-muted">Then paste the token shown in your terminal:</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useLocalStorage } from '@vueuse/core'
2+
3+
export function useSelectedPackageManager() {
4+
return useLocalStorage<PackageManagerId>('npmx-pm', 'npm')
5+
}

app/pages/[...package].vue

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,7 @@ function hasProvenance(version: PackumentVersion | null): boolean {
190190
return !!dist.attestations
191191
}
192192
193-
// Persist package manager preference in localStorage
194-
const selectedPM = ref<PackageManagerId>('npm')
195-
196-
onMounted(() => {
197-
const stored = localStorage.getItem('npmx-pm')
198-
if (stored && packageManagers.some(pm => pm.id === stored)) {
199-
selectedPM.value = stored as PackageManagerId
200-
}
201-
})
202-
203-
watch(selectedPM, value => {
204-
localStorage.setItem('npmx-pm', value)
205-
})
193+
const selectedPM = useSelectedPackageManager()
206194
207195
const installCommandParts = computed(() => {
208196
if (!pkg.value) return []

app/utils/install-command.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { JsrPackageInfo } from '#shared/types/jsr'
22

33
export const packageManagers = [
4-
{ id: 'npm', label: 'npm', action: 'install' },
5-
{ id: 'pnpm', label: 'pnpm', action: 'add' },
6-
{ id: 'yarn', label: 'yarn', action: 'add' },
7-
{ id: 'bun', label: 'bun', action: 'add' },
8-
{ id: 'deno', label: 'deno', action: 'add' },
9-
{ id: 'vlt', label: 'vlt', action: 'install' },
4+
{ id: 'npm', label: 'npm', action: 'install', execute: 'npx' },
5+
{ id: 'pnpm', label: 'pnpm', action: 'add', execute: 'pnpm dlx' },
6+
{ id: 'yarn', label: 'yarn', action: 'add', execute: 'yarn dlx' },
7+
{ id: 'bun', label: 'bun', action: 'add', execute: 'bunx' },
8+
{ id: 'deno', label: 'deno', action: 'add', execute: 'deno run' },
9+
{ id: 'vlt', label: 'vlt', action: 'install', execute: 'vlt x' },
1010
] as const
1111

1212
export type PackageManagerId = (typeof packageManagers)[number]['id']
@@ -59,3 +59,13 @@ export function getInstallCommandParts(options: InstallCommandOptions): string[]
5959

6060
return [pm.label, pm.action, `${spec}${version}`]
6161
}
62+
63+
export function getExecuteCommand(options: InstallCommandOptions): string {
64+
return getExecuteCommandParts(options).join(' ')
65+
}
66+
67+
export function getExecuteCommandParts(options: InstallCommandOptions): string[] {
68+
const pm = packageManagers.find(p => p.id === options.packageManager)
69+
if (!pm) return []
70+
return [pm.execute, getPackageSpecifier(options)]
71+
}

0 commit comments

Comments
 (0)