Spaces:
Running
Running
| <!-- src/components/AssetTabsPrice.vue --> | |
| <template> | |
| <div class="tabs"> | |
| <button v-for="code in orderedAssets" :key="code" | |
| class="tab" :class="code===modelValue ? 'tab--active' : ''" | |
| @click="$emit('update:modelValue', code)"> | |
| <div class="row"> | |
| <img :src="iconFor(code)" class="icon" @error="hide($event)" alt="" /> | |
| <span class="code">{{ code }}</span> | |
| </div> | |
| <div class="price">{{ fmtUSD(prices[code]) }}</div> | |
| </button> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue' | |
| import { dataService } from '../lib/dataService' | |
| import { pollPrices, subscribePrices, fmtUSD } from '../lib/prices' | |
| const props = defineProps({ modelValue: String }) | |
| const emit = defineEmits(['update:modelValue']) | |
| const iconFor = (c:string) => new URL(`../assets/images/assets_images/${c}.png`, import.meta.url).href | |
| const hide = (e:Event)=>((e.target as HTMLImageElement).style.display='none') | |
| const available = computed(() => { | |
| const rows = Array.isArray((dataService as any).tableRows) ? (dataService as any).tableRows : [] | |
| return Array.from(new Set(rows.map((r:any)=>r.asset))) | |
| }) | |
| const preferred = ['BTC','ETH','SOL','BNB','DOGE','XRP','AAPL','MSFT','BMRN','MRNA','TSLA'] | |
| const orderedAssets = computed(() => { | |
| const present = new Set(available.value) | |
| const primary = preferred.filter(a => present.has(a)) | |
| const extras = [...present].filter(a => !preferred.includes(a)).sort() | |
| const list = [...primary, ...extras] | |
| if (list.length && !list.includes(props.modelValue || '')) emit('update:modelValue', list[0]) | |
| return list | |
| }) | |
| const prices = ref<Record<string, number>>({}) | |
| let unsub: null | (()=>void) = null | |
| onMounted(async () => { | |
| unsub = subscribePrices(m => (prices.value = m)) | |
| await pollPrices(orderedAssets.value) // starts polling; cheap even if called again | |
| }) | |
| onBeforeUnmount(()=>unsub?.()) | |
| watch(orderedAssets, (arr)=>pollPrices(arr)) | |
| </script> | |
| <style scoped> | |
| .tabs{ display:flex; flex-wrap:wrap; gap:8px } | |
| .tab{ | |
| background:#fff; border:1px solid #1f2937; border-radius:9999px; | |
| padding:8px 12px; min-width:110px; display:flex; flex-direction:column; align-items:center; gap:2px | |
| } | |
| .tab--active{ background:#111827; color:#fff; border-color:#111827 } | |
| .row{ display:flex; align-items:center; gap:6px; line-height:1 } | |
| .icon{ width:18px; height:18px; flex:0 0 18px; object-fit:contain } | |
| .code{ font-weight:700; letter-spacing:.02em } | |
| .price{ font-variant-numeric:tabular-nums; font-weight:800; line-height:1.05; margin-top:2px } | |
| </style> | |