[READ-ONLY] a fast, modern browser for the npm registry
at main 243 lines 8.2 kB view raw
1<script setup lang="ts"> 2const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } = 3 useConnector() 4 5const { settings } = useSettings() 6 7const tokenInput = shallowRef('') 8const portInput = shallowRef('31415') 9const { copied, copy } = useClipboard({ copiedDuring: 2000 }) 10 11const hasAttemptedConnect = shallowRef(false) 12 13watch(isConnected, connected => { 14 if (!connected) { 15 tokenInput.value = '' 16 hasAttemptedConnect.value = false 17 } 18}) 19 20async function handleConnect() { 21 hasAttemptedConnect.value = true 22 const port = Number.parseInt(portInput.value, 10) || 31415 23 await connect(tokenInput.value.trim(), port) 24} 25 26function handleDisconnect() { 27 disconnect() 28} 29 30// function copyCommand() { 31// let command = executeNpmxConnectorCommand.value 32// if (portInput.value !== '31415') { 33// command += ` --port ${portInput.value}` 34// } 35// copy(command) 36// } 37 38// const selectedPM = useSelectedPackageManager() 39 40// const executeNpmxConnectorCommand = computed(() => { 41// return getExecuteCommand({ 42// packageName: 'npmx-connector', 43// packageManager: selectedPM.value, 44// }) 45// }) 46</script> 47 48<template> 49 <Modal 50 :modalTitle="$t('connector.modal.title')" 51 :class="isConnected && hasOperations ? 'max-w-2xl' : 'max-w-md'" 52 id="connector-modal" 53 > 54 <!-- Connected state --> 55 <div v-if="isConnected" class="space-y-4"> 56 <div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg"> 57 <span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" /> 58 <div> 59 <p class="font-mono text-sm text-fg">{{ $t('connector.modal.connected') }}</p> 60 <p v-if="npmUser" class="font-mono text-xs text-fg-muted"> 61 {{ $t('connector.modal.connected_as_user', { user: npmUser }) }} 62 </p> 63 </div> 64 </div> 65 66 <!-- Connector preferences --> 67 <div class="flex flex-col gap-2"> 68 <SettingsToggle 69 :label="$t('connector.modal.auto_open_url')" 70 v-model="settings.connector.autoOpenURL" 71 /> 72 </div> 73 74 <div class="border-t border-border my-3" /> 75 76 <!-- Operations Queue --> 77 <OrgOperationsQueue /> 78 79 <div v-if="!hasOperations" class="text-sm text-fg-muted"> 80 {{ $t('connector.modal.connected_hint') }} 81 </div> 82 83 <ButtonBase type="button" class="w-full" @click="handleDisconnect"> 84 {{ $t('connector.modal.disconnect') }} 85 </ButtonBase> 86 </div> 87 88 <!-- Disconnected state --> 89 <form v-else class="space-y-4" @submit.prevent="handleConnect"> 90 <!-- Contributor-only notice --> 91 <div class="p-3 bg-amber-500/10 border border-amber-500/30 rounded-lg"> 92 <div> 93 <span class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded"> 94 {{ $t('connector.modal.contributor_badge') }} 95 </span> 96 <p class="text-sm text-fg-muted"> 97 <i18n-t keypath="connector.modal.contributor_notice" scope="global"> 98 <template #link> 99 <LinkBase 100 to="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#local-connector-cli" 101 > 102 {{ $t('connector.modal.contributor_link') }} 103 </LinkBase> 104 </template> 105 </i18n-t> 106 </p> 107 </div> 108 </div> 109 110 <p class="text-sm text-fg-muted"> 111 {{ $t('connector.modal.run_hint') }} 112 </p> 113 114 <div 115 class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm" 116 dir="ltr" 117 > 118 <span class="text-fg-subtle">$</span> 119 <span class="text-fg-subtle ms-2">pnpm npmx-connector</span> 120 <ButtonBase 121 :aria-label="copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')" 122 @click="copy('pnpm npmx-connector')" 123 class="ms-auto" 124 :classicon="copied ? 'i-lucide:check text-green-500' : 'i-lucide:copy'" 125 /> 126 </div> 127 128 <!-- TODO: Uncomment when npmx-connector is published to npm 129 <div 130 class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm" 131 > 132 <span class="text-fg-subtle">$</span> 133 <span class="text-fg-subtle ms-2">{{ executeNpmxConnectorCommand }}</span> 134 <div class="ms-auto flex items-center gap-2"> 135 <PackageManagerSelect /> 136 137 <button 138 type="button" 139 :aria-label=" 140 copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command') 141 " 142 class="ms-auto text-fg-subtle p-1.5 -m-1.5 hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded" 143 @click="copyCommand" 144 > 145 <span v-if="!copied" class="i-lucide:copy block w-5 h-5" aria-hidden="true" /> 146 <span 147 v-else 148 class="i-lucide:check block w-5 h-5 text-green-500" 149 aria-hidden="true" 150 /> 151 </button> 152 </div> 153 </div> 154 --> 155 156 <p class="text-sm text-fg-muted">{{ $t('connector.modal.paste_token') }}</p> 157 158 <div class="space-y-3"> 159 <div> 160 <label 161 for="connector-token" 162 class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" 163 > 164 {{ $t('connector.modal.token_label') }} 165 </label> 166 <InputBase 167 id="connector-token" 168 v-model="tokenInput" 169 type="password" 170 name="connector-token" 171 :placeholder="$t('connector.modal.token_placeholder')" 172 no-correct 173 class="w-full" 174 size="medium" 175 /> 176 </div> 177 178 <details class="text-sm"> 179 <summary class="text-fg-subtle hover:text-fg-muted transition-colors duration-200"> 180 {{ $t('connector.modal.advanced') }} 181 </summary> 182 <div class="mt-3"> 183 <label 184 for="connector-port" 185 class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" 186 > 187 {{ $t('connector.modal.port_label') }} 188 </label> 189 <InputBase 190 id="connector-port" 191 v-model="portInput" 192 type="text" 193 name="connector-port" 194 inputmode="numeric" 195 autocomplete="off" 196 class="w-full" 197 size="medium" 198 /> 199 200 <div class="border-t border-border my-3" /> 201 <div class="flex flex-col gap-2"> 202 <SettingsToggle 203 :label="$t('connector.modal.auto_open_url')" 204 v-model="settings.connector.autoOpenURL" 205 /> 206 </div> 207 </div> 208 </details> 209 </div> 210 211 <!-- Error message (only show after user explicitly clicks Connect) --> 212 <div 213 v-if="error && hasAttemptedConnect" 214 role="alert" 215 class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md" 216 > 217 {{ error }} 218 </div> 219 220 <!-- Warning message --> 221 <div 222 role="alert" 223 class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md" 224 > 225 <p class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded"> 226 {{ $t('connector.modal.warning') }} 227 </p> 228 <p class="text-sm text-fg-muted mt-1"> 229 {{ $t('connector.modal.warning_text') }} 230 </p> 231 </div> 232 233 <ButtonBase 234 type="submit" 235 variant="primary" 236 :disabled="!tokenInput.trim() || isConnecting" 237 class="w-full" 238 > 239 {{ isConnecting ? $t('connector.modal.connecting') : $t('connector.modal.connect') }} 240 </ButtonBase> 241 </form> 242 </Modal> 243</template>