refactor: split Web5.vue, Settings.vue, and Mesh.vue into focused subcomponents

- F25: Split Web5.vue (3940 lines) into 14 files under views/web5/
- F26: Split Mesh.vue (2106→840 lines) extracting Bitcoin and Deadman panels
- F27: Dashboard.vue assessed — layout shell, no split needed
- F28: Split Settings.vue (1792 lines) into AccountSection + SystemSection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-21 02:43:28 +00:00
co-authored by Claude Opus 4.6
parent 618244eab0
commit afd7405b1a
21 changed files with 6325 additions and 3201 deletions
@@ -0,0 +1,135 @@
<template>
<!-- Node Visibility -->
<div data-controller-container tabindex="0" :class="{ 'card-stagger': showStagger }" class="glass-card p-6 flex flex-col md:w-1/2" style="--stagger-index: 3">
<div class="flex items-start gap-4 mb-4 shrink-0">
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
<svg class="w-6 h-6 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</div>
<div class="flex-1">
<h2 class="text-xl font-semibold text-white mb-2">{{ t('web5.nodeVisibility') }}</h2>
<p class="text-white/70 text-sm mb-4">{{ t('web5.nodeVisibilityDesc') }}</p>
</div>
<div v-if="visibilityLoading" class="shrink-0">
<svg class="animate-spin h-5 w-5 text-white/40" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</div>
<!-- Visibility Options -->
<div class="space-y-2 flex-1 min-h-0">
<button
v-for="opt in visibilityOptions"
:key="opt.value"
@click="setVisibility(opt.value)"
:disabled="settingVisibility"
class="w-full flex items-center gap-3 p-3 rounded-lg border transition-colors text-left"
:class="nodeVisibility === opt.value
? 'bg-white/10 border-white/25 text-white'
: 'bg-white/5 border-white/10 text-white/60 hover:bg-white/8 hover:text-white/80'"
>
<div class="w-3 h-3 rounded-full shrink-0 border-2 flex items-center justify-center"
:class="nodeVisibility === opt.value ? 'border-green-400' : 'border-white/30'"
>
<div v-if="nodeVisibility === opt.value" class="w-1.5 h-1.5 rounded-full bg-green-400"></div>
</div>
<div class="min-w-0 flex-1">
<p class="text-sm font-medium">{{ opt.label }}</p>
<p class="text-xs text-white/50">{{ opt.description }}</p>
</div>
</button>
</div>
<!-- Onion address (shown when discoverable/public) -->
<div v-if="nodeVisibility !== 'hidden' && nodeOnionAddress" class="mt-4 p-3 bg-white/5 rounded-lg">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0">
<p class="text-xs text-white/50 mb-1">{{ t('web5.yourTorAddress') }}</p>
<p class="text-xs font-mono text-white/80 truncate" :title="nodeOnionAddress">{{ nodeOnionAddress }}</p>
</div>
<button @click="copyOnionAddress" class="shrink-0 p-2 rounded-lg text-white/50 hover:text-white hover:bg-white/10 transition-colors" title="Copy">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
</svg>
</button>
</div>
</div>
<!-- Warning -->
<p v-if="nodeVisibility !== 'hidden'" class="mt-3 text-xs text-amber-400/80">
{{ t('web5.discoverableWarning') }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { rpcClient } from '@/api/rpc-client'
import { safeClipboardWrite } from './utils'
import type { VisibilityLevel } from './types'
const { t } = useI18n()
defineProps<{
showStagger: boolean
}>()
const emit = defineEmits<{
toast: [text: string]
}>()
const nodeVisibility = ref<VisibilityLevel>('hidden')
const nodeOnionAddress = ref<string | null>(null)
const visibilityLoading = ref(false)
const settingVisibility = ref(false)
const visibilityOptions = [
{ value: 'hidden' as VisibilityLevel, label: 'Hidden', description: 'Your node is not discoverable by others' },
{ value: 'discoverable' as VisibilityLevel, label: 'Discoverable', description: 'Federated peers can find and connect to your node' },
{ value: 'public' as VisibilityLevel, label: 'Public', description: 'Accepting connections from any Archipelago node' },
]
async function loadVisibility() {
visibilityLoading.value = true
try {
const res = await rpcClient.call<{ visibility: string; onion_address?: string }>({ method: 'network.get-visibility' })
nodeVisibility.value = (res.visibility as VisibilityLevel) || 'hidden'
nodeOnionAddress.value = res.onion_address || null
} catch {
nodeVisibility.value = 'hidden'
} finally {
visibilityLoading.value = false
}
}
async function setVisibility(level: VisibilityLevel) {
if (settingVisibility.value || nodeVisibility.value === level) return
settingVisibility.value = true
try {
const res = await rpcClient.call<{ visibility: string; onion_address?: string }>({
method: 'network.set-visibility',
params: { visibility: level },
})
nodeVisibility.value = (res.visibility as VisibilityLevel) || level
nodeOnionAddress.value = res.onion_address || nodeOnionAddress.value
emit('toast', t('web5.visibilitySetTo', { level }))
} catch {
emit('toast', t('web5.failedToUpdateVisibility'))
} finally {
settingVisibility.value = false
}
}
function copyOnionAddress() {
if (!nodeOnionAddress.value) return
safeClipboardWrite(nodeOnionAddress.value)
emit('toast', t('web5.onionAddressCopied'))
}
defineExpose({ loadVisibility })
</script>