Merge pull request #380 from alisaweb3/v3-single

Timeline, Wallet Drapdown,Validator Detail,Governance Progress
This commit is contained in:
ping
2023-05-15 14:07:19 +08:00
committed by GitHub
34 changed files with 755 additions and 1016 deletions
+148 -253
View File
@@ -93,7 +93,7 @@ loadAccount(props.address);
<div v-if="account">
<!-- address -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="d-flex items-center">
<div class="flex items-center">
<!-- img -->
<div class="inline-flex relative w-11 h-11 rounded-md">
<div
@@ -126,7 +126,7 @@ loadAccount(props.address);
</div>
<div class="mt-4 md:col-span-2 md:mt-0 md:ml-4">
<!-- button -->
<div class="d-flex justify-end mb-4">
<div class="flex justify-end mb-4 pr-5">
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@@ -144,74 +144,105 @@ loadAccount(props.address);
>transfer</label
>
</div>
<!-- -->
<!-- list-->
<div class="">
<VListItem v-for="v in balances">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="info">
<VIcon icon="mdi-account-cash" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs"> ${{ 0 }} </VListItemSubtitle>
<template #append>
<!--balances -->
<div
class="flex items-center px-4 mb-2"
v-for="(balanceItem, index) in balances"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-account-cash" class="text-info" size="20" />
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.amount, totalAmount) }}
class="absolute top-0 bottom-0 left-0 right-0 bg-info opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
{{ format.formatToken(balanceItem) }}
</div>
</template>
</VListItem>
<VListItem v-for="v in delegations">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="warning">
<VIcon icon="mdi-user-clock" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v.balance) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs"> ${{ 0 }} </VListItemSubtitle>
<template #append>
<div class="text-xs">${{ 0 }}</div>
</div>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary text-sm"
></span>
{{ format.calculatePercent(balanceItem.amount, totalAmount) }}
</div>
</div>
<!--delegations -->
<div
class="flex items-center px-4 mb-2"
v-for="(delegationItem, index) in delegations"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-user-clock" class="text-warning" size="20" />
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.balance.amount, totalAmount) }}
class="absolute top-0 bottom-0 left-0 right-0 bg-warning opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
{{ format.formatToken(delegationItem?.balance) }}
</div>
</template>
</VListItem>
<VListItem v-for="v in rewards.total">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="success">
<VIcon icon="mdi-account-arrow-up" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs"> ${{ 0 }} </VListItemSubtitle>
<template #append>
<div class="text-xs">${{ 0 }}</div>
</div>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary text-sm"
></span>
{{
format.calculatePercent(
delegationItem?.balance?.amount,
totalAmount
)
}}
</div>
</div>
<!-- rewards.total -->
<div
class="flex items-center px-4 mb-2"
v-for="(rewardItem, index) in rewards.total"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon
icon="mdi-account-arrow-up"
class="text-success"
size="20"
/>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.amount, totalAmount) }}
class="absolute top-0 bottom-0 left-0 right-0 bg-success opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
{{ format.formatToken(rewardItem) }}
</div>
</template>
</VListItem>
<div class="text-xs">${{ 0 }}</div>
</div>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary text-sm"
></span>
{{ format.calculatePercent(rewardItem.amount, totalAmount) }}
</div>
</div>
<!-- mdi-account-arrow-right -->
<div class="flex items-center px-4">
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
@@ -225,9 +256,8 @@ loadAccount(props.address);
class="absolute top-0 bottom-0 left-0 right-0 bg-error opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-base font-semibold">
<div class="text-sm font-semibold">
{{
format.formatToken({
amount: String(unbondingTotal),
@@ -235,10 +265,10 @@ loadAccount(props.address);
})
}}
</div>
<div class="text-sm">${{ 0 }}</div>
<div class="text-xs">${{ 0 }}</div>
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
@@ -247,153 +277,17 @@ loadAccount(props.address);
</div>
</div>
</div>
<div class="divider"></div>
{{ totalAmount }}
<div class="mt-4 text-lg font-semibold mr-5 pl-5 border-t pt-4">
{{ totalAmount }}
</div>
</div>
</div>
</div>
<VCard class="mt-5">
<VCardTitle>Assets</VCardTitle>
<VCardItem>
<VRow>
<VCol cols="12" md="4">
<DonutChart :series="totalAmountByCategory" :labels="labels" />
</VCol>
<VCol cols="12" md="8">
<VList class="card-list">
<VListItem>
<label
for="transfer"
class="btn btn-primary float-right btn-sm"
@click="
dialog.open('transfer', {
chain_name: blockchain.current?.prettyName,
})
"
>transfer</label
>
<label
for="send"
class="btn btn-primary float-right btn-sm"
@click="dialog.open('send', {})"
>Send</label
>
</VListItem>
<VListItem v-for="v in balances">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="info">
<VIcon icon="mdi-account-cash" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.amount, totalAmount) }}
</div>
</template>
</VListItem>
<VListItem v-for="v in delegations">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="warning">
<VIcon icon="mdi-user-clock" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v.balance) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.balance.amount, totalAmount) }}
</div>
</template>
</VListItem>
<VListItem v-for="v in rewards.total">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="success">
<VIcon icon="mdi-account-arrow-up" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(v.amount, totalAmount) }}
</div>
</template>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="error">
<VIcon icon="mdi-account-arrow-right" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{
format.formatToken({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit text-primary mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary"
></span>
{{ format.calculatePercent(unbondingTotal, totalAmount) }}
</div>
</template>
</VListItem>
</VList>
<VDivider class="my-2"></VDivider>
{{ totalAmount }}
</VCol>
</VRow>
</VCardItem>
</VCard>
<!-- Delegations -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title mb-4">Delegations</h2>
<div class="d-flex justify-end mb-4">
<div class="flex justify-end mb-4">
<label
for="delegate"
class="btn btn-primary btn-sm mr-2"
@@ -408,18 +302,18 @@ loadAccount(props.address);
>
</div>
<div class="overdflow-x-auto">
<table class="table w-full">
<table class="table w-full text-sm table-zebra">
<thead>
<tr>
<th style="position: relative">Validator</th>
<th>Delegation</th>
<th>Rewards</th>
<th>Action</th>
<th class="py-3">Validator</th>
<th class="py-3">Delegation</th>
<th class="py-3">Rewards</th>
<th class="py-3">Action</th>
</tr>
</thead>
<tbody class="text-sm">
<tr v-for="v in delegations">
<td class="text-caption text-primary">
<tr v-for="(v, index) in delegations" :key="index">
<td class="text-caption text-primary py-3">
<RouterLink
:to="`/${chain}/staking/${v.delegation.validator_address}`"
>{{
@@ -427,8 +321,10 @@ loadAccount(props.address);
}}</RouterLink
>
</td>
<td>{{ format.formatToken(v.balance, true, '0,0.[00]') }}</td>
<td>
<td class="py-3">
{{ format.formatToken(v.balance, true, '0,0.[00]') }}
</td>
<td class="py-3">
{{
format.formatTokens(
rewards?.rewards?.find(
@@ -438,11 +334,11 @@ loadAccount(props.address);
)
}}
</td>
<td>
<div class="d-flex justify-end">
<td class="py-3">
<div class="flex justify-end">
<label
for="delegate"
class="btn btn-primary btn-sm mr-2"
class="btn btn-primary btn-xs mr-2"
@click="
dialog.open('delegate', {
validator_address: v.delegation.validator_address,
@@ -452,7 +348,7 @@ loadAccount(props.address);
>
<label
for="redelegate"
class="btn btn-primary btn-sm mr-2"
class="btn btn-primary btn-xs mr-2"
@click="
dialog.open('redelegate', {
validator_address: v.delegation.validator_address,
@@ -462,7 +358,7 @@ loadAccount(props.address);
>
<label
for="unbond"
class="btn btn-primary btn-sm"
class="btn btn-primary btn-xs"
@click="
dialog.open('unbond', {
validator_address: v.delegation.validator_address,
@@ -485,19 +381,19 @@ loadAccount(props.address);
>
<h2 class="card-title mb-4">Unbonding Delegations</h2>
<div class="overflow-x-auto">
<table class="table">
<table class="table text-sm w-full">
<thead>
<tr>
<th style="position: relative">Creation Height</th>
<th>Initial Balance</th>
<th>Balance</th>
<th>Completion Time</th>
<th class="py-3">Creation Height</th>
<th class="py-3">Initial Balance</th>
<th class="py-3">Balance</th>
<th class="py-3">Completion Time</th>
</tr>
</thead>
<tbody class="text-sm">
<div v-for="v in unbonding">
<div v-for="(v, index) in unbonding" :key="index">
<tr>
<td class="text-caption text-primary">
<td class="text-caption text-primary py-3">
<RouterLink
:to="`/${chain}/staking/${v.validator_address}`"
>{{
@@ -507,8 +403,8 @@ loadAccount(props.address);
</td>
</tr>
<tr v-for="entry in v.entries">
<td>{{ entry.creation_height }}</td>
<td>
<td class="py-3">{{ entry.creation_height }}</td>
<td class="py-3">
{{
format.formatToken(
{
@@ -520,7 +416,7 @@ loadAccount(props.address);
)
}}
</td>
<td>
<td class="py-3">
{{
format.formatToken(
{
@@ -532,7 +428,9 @@ loadAccount(props.address);
)
}}
</td>
<td>{{ format.toDay(entry.completion_time, 'to') }}</td>
<td class="py-3">
{{ format.toDay(entry.completion_time, 'to') }}
</td>
</tr>
</div>
</tbody>
@@ -544,35 +442,37 @@ loadAccount(props.address);
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title mb-4">Transactions</h2>
<div class="overflow-x-auto">
<table class="table w-full">
<table class="table w-full text-sm">
<thead>
<tr>
<th style="position: relative">Height</th>
<th>Hash</th>
<th>Messages</th>
<th>Time</th>
<th class="py-3">Height</th>
<th class="py-3">Hash</th>
<th class="py-3">Messages</th>
<th class="py-3">Time</th>
</tr>
</thead>
<tbody class="text-sm">
<tr v-for="v in txs">
<td class="text-sm text-primary">
<tr v-for="(v, index) in txs" :key="index">
<td class="text-sm text-primary py-3">
<RouterLink :to="`/${chain}/block/${v.height}`">{{
v.height
}}</RouterLink>
</td>
<td class="text-truncate" style="max-width: 200px">
<td class="text-truncate py-3" style="max-width: 200px">
{{ v.txhash }}
</td>
<td>
{{ format.messages(v.tx.body.messages) }}
<VIcon
<td class="flex items-center py-3">
<div class="mr-2">
{{ format.messages(v.tx.body.messages) }}
</div>
<Icon
v-if="v.code === 0"
icon="mdi-check"
color="success"
></VIcon>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon>
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td>{{ format.toDay(v.timestamp, 'from') }}</td>
<td class="py-3">{{ format.toDay(v.timestamp, 'from') }}</td>
</tr>
</tbody>
</table>
@@ -585,10 +485,5 @@ loadAccount(props.address);
<DynamicComponent :value="account" />
</div>
</div>
<div v-else>Account does not exists on chain</div>
<div v-else class="text-no text-sm">Account does not exists on chain</div>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 5px;
}
</style>
+2 -2
View File
@@ -55,13 +55,13 @@ export const useBlockModule = defineStore('blockModule', {
});
},
async fetchLatest() {
this.latest = await this.blockchain.rpc.getBaseBlockLatest();
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
if (this.recents.length >= 50) this.recents.shift();
this.recents.push(this.latest);
return this.latest;
},
async fetchBlock(height: string) {
this.current = await this.blockchain.rpc.getBaseBlockAt(height);
this.current = await this.blockchain.rpc?.getBaseBlockAt(height);
return this.current;
},
},
+3 -3
View File
@@ -30,7 +30,7 @@ const format = useFormatter();
<table class="table w-full">
<thead>
<tr>
<th style="position: relative">Height</th>
<th style="position: relative; z-index: 2;">Height</th>
<th>Hash</th>
<th>Proposer</th>
<th>Txs</th>
@@ -63,8 +63,8 @@ const format = useFormatter();
<table class="table w-full">
<thead>
<tr>
<th style="position: relative">Height</th>
<th style="position: relative">Hash</th>
<th style="position: relative; z-index: 2;">Height</th>
<th style="position: relative; z-index: 2;">Hash</th>
<th>Messages</th>
<th>Fees</th>
</tr>
@@ -29,7 +29,7 @@ const selected = ref('');
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
infoDialog.value = true;
// infoDialog.value = true;
});
}
function showState(address: string) {
@@ -99,20 +99,21 @@ const result = ref('');
<table class="table w-full mt-4">
<thead>
<tr>
<th style="position: relative">Contract List</th>
<th style="position: relative; z-index: 2">Contract List</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="v in response.contracts">
<tr v-for="(v, index) in response.contracts" :key="index">
<td>{{ v }}</td>
<td>
<button
class="btn btn-primary btn-sm text-xs mr-2"
<label
@click="showInfo(v)"
for="modal-contract-detail"
class="btn btn-primary btn-sm text-xs mr-2"
>contract</label
>
contract
</button>
<button
class="btn btn-primary btn-sm text-xs mr-2"
@click="showState(v)"
@@ -132,19 +133,25 @@ const result = ref('');
</div>
</div>
<v-dialog v-model="infoDialog" width="auto">
<v-card>
<VCardTitle>Contract Detail</VCardTitle>
<v-card-text>
<DynamicComponent :value="info" />
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="infoDialog = false"
>Close Dialog</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<input type="checkbox" id="modal-contract-detail" class="modal-toggle" />
<label for="modal-contract-detail" class="modal cursor-pointer">
<label class="modal-box relative p-2" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">Contract Detail</div>
<label
@click="infoDialog = false"
for="modal-contract-detail"
class="btn btn-sm btn-circle"
></label
>
</div>
<div>
<DynamicComponent :value="info" />
</div>
</div>
</label>
</label>
<v-dialog v-model="stateDialog" width="auto">
<v-card>
+1 -1
View File
@@ -20,7 +20,7 @@ wasmStore.wasmClient.getWasmCodeList().then((x) => {
<table class="table w-full mt-4 text-sm">
<thead>
<tr>
<th style="position: relative">Code Id</th>
<th style="position: relative; z-index: 2;">Code Id</th>
<th>Code Hash</th>
<th>Creator</th>
<th>Permissions</th>
+308 -273
View File
@@ -1,318 +1,353 @@
<script lang="ts" setup>
import ObjectElement from '@/components/dynamic/ObjectElement.vue';
import { useBaseStore, useFormatter, useGovStore, useStakingStore, useTxDialog } from '@/stores';
import type { GovProposal, GovVote, PaginabledAccounts, PaginatedProposalDeposit, PaginatedProposalVotes, Pagination } from '@/types';
import { ref , reactive} from 'vue';
import {
useBaseStore,
useFormatter,
useGovStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import type {
GovProposal,
GovVote,
PaginatedProposalDeposit,
Pagination,
} from '@/types';
import { ref, reactive } from 'vue';
import Countdown from '@/components/Countdown.vue';
import { computed } from '@vue/reactivity';
const props = defineProps(["proposal_id", "chain"]);
const proposal = ref({} as GovProposal)
const format = useFormatter()
const store = useGovStore()
const dialog = useTxDialog()
const props = defineProps(['proposal_id', 'chain']);
const proposal = ref({} as GovProposal);
const format = useFormatter();
const store = useGovStore();
const dialog = useTxDialog();
store.fetchProposal(props.proposal_id).then((res) => {
const proposalDetail = reactive(res.proposal)
// when status under the voting, final_tally_result are no data, should request fetchTally
if (res.proposal?.status === 'PROPOSAL_STATUS_VOTING_PERIOD'){
store.fetchTally(props.proposal_id).then((tallRes)=>{
proposalDetail.final_tally_result = tallRes?.tally
})
}
proposal.value = proposalDetail
})
const proposalDetail = reactive(res.proposal);
// when status under the voting, final_tally_result are no data, should request fetchTally
if (res.proposal?.status === 'PROPOSAL_STATUS_VOTING_PERIOD') {
store.fetchTally(props.proposal_id).then((tallRes) => {
proposalDetail.final_tally_result = tallRes?.tally;
});
}
proposal.value = proposalDetail;
});
const color = computed(() => {
if (proposal.value.status==='PROPOSAL_STATUS_PASSED') {
return "success"
}else if (proposal.value.status==='PROPOSAL_STATUS_REJECTED') {
return "error"
}
return ""
})
if (proposal.value.status === 'PROPOSAL_STATUS_PASSED') {
return 'success';
} else if (proposal.value.status === 'PROPOSAL_STATUS_REJECTED') {
return 'error';
}
return '';
});
const status = computed(() => {
if(proposal.value.status) {
return proposal.value.status.replace("PROPOSAL_STATUS_", "")
}
return ""
})
if (proposal.value.status) {
return proposal.value.status.replace('PROPOSAL_STATUS_', '');
}
return '';
});
const deposit = ref({} as PaginatedProposalDeposit)
store.fetchProposalDeposits(props.proposal_id).then(x => deposit.value = x)
const deposit = ref({} as PaginatedProposalDeposit);
store.fetchProposalDeposits(props.proposal_id).then((x) => (deposit.value = x));
const votes = ref({} as GovVote[])
const votePage = ref({} as Pagination)
const loading = ref(false)
const votes = ref({} as GovVote[]);
const votePage = ref({} as Pagination);
const loading = ref(false);
store.fetchProposalVotes(props.proposal_id).then(x => {
votes.value = x.votes
votePage.value = x.pagination
})
store.fetchProposalVotes(props.proposal_id).then((x) => {
votes.value = x.votes;
votePage.value = x.pagination;
});
function loadMore() {
if(votePage.value.next_key) {
loading.value = true
store.fetchProposalVotes(props.proposal_id, votePage.value.next_key).then(x => {
votes.value = votes.value.concat(x.votes)
votePage.value = x.pagination
loading.value = false
})
}
if (votePage.value.next_key) {
loading.value = true;
store
.fetchProposalVotes(props.proposal_id, votePage.value.next_key)
.then((x) => {
votes.value = votes.value.concat(x.votes);
votePage.value = x.pagination;
loading.value = false;
});
}
}
function shortTime(v: string) {
if(v) {
return format.toDay(v, "from")
}
return ""
if (v) {
return format.toDay(v, 'from');
}
return '';
}
const votingCountdown = computed((): number => {
const now = new Date();
const end = new Date(proposal.value.voting_end_time)
return end.getTime() - now.getTime()
})
const now = new Date();
const end = new Date(proposal.value.voting_end_time);
return end.getTime() - now.getTime();
});
const upgradeCountdown = computed((): number => {
const height = Number(proposal.value.content?.plan?.height || 0)
if(height > 0) {
const base = useBaseStore()
const current = Number(base.latest?.block?.header?.height || 0)
return (height - current) * 6 * 1000
}
const now = new Date();
const end = new Date(proposal.value.content?.plan?.time || "")
return end.getTime() - now.getTime()
})
const height = Number(proposal.value.content?.plan?.height || 0);
if (height > 0) {
const base = useBaseStore();
const current = Number(base.latest?.block?.header?.height || 0);
return (height - current) * 6 * 1000;
}
const now = new Date();
const end = new Date(proposal.value.content?.plan?.time || '');
return end.getTime() - now.getTime();
});
const total = computed(()=> {
const tally = proposal.value.final_tally_result
let sum = 0
if(tally) {
sum += Number(tally.abstain || 0)
sum += Number(tally.yes || 0)
sum += Number(tally.no || 0)
sum += Number(tally.no_with_veto || 0)
}
return sum
})
const total = computed(() => {
const tally = proposal.value.final_tally_result;
let sum = 0;
if (tally) {
sum += Number(tally.abstain || 0);
sum += Number(tally.yes || 0);
sum += Number(tally.no || 0);
sum += Number(tally.no_with_veto || 0);
}
return sum;
});
const turnout = computed(() => {
if (total.value > 0) {
const bonded = useStakingStore().pool?.bonded_tokens || "1"
return format.percent(total.value / Number(bonded))
}
return 0
})
if (total.value > 0) {
const bonded = useStakingStore().pool?.bonded_tokens || '1';
return format.percent(total.value / Number(bonded));
}
return 0;
});
const yes = computed(()=> {
if(total.value > 0) {
const yes = proposal.value?.final_tally_result?.yes || 0
return format.percent(Number(yes) / total.value )
}
return 0
})
const yes = computed(() => {
if (total.value > 0) {
const yes = proposal.value?.final_tally_result?.yes || 0;
return format.percent(Number(yes) / total.value);
}
return 0;
});
const no = computed(()=> {
if(total.value > 0) {
const value = proposal.value?.final_tally_result?.no || 0
return format.percent(Number(value) / total.value)
}
return 0
})
const no = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.no || 0;
return format.percent(Number(value) / total.value);
}
return 0;
});
const veto = computed(()=> {
if(total.value > 0) {
const value = proposal.value?.final_tally_result?.no_with_veto || 0
return format.percent(Number(value) / total.value)
}
return 0
})
const veto = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.no_with_veto || 0;
return format.percent(Number(value) / total.value);
}
return 0;
});
const abstain = computed(()=> {
if(total.value > 0) {
const value = proposal.value?.final_tally_result?.abstain || 0
return format.percent(Number(value) / total.value)
}
return 0
})
const processList = computed(()=>{
return [
{name: 'Turnout', value : turnout.value, class: 'bg-info' },
{name: 'Yes', value : yes.value, class: 'bg-success' },
{name: 'No', value : no.value, class: 'bg-error' },
{name: 'No With Veto', value : veto.value, class: 'bg-primary' },
{name: 'Abstain', value : abstain.value, class: 'bg-warning' }
]
})
const abstain = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.abstain || 0;
return format.percent(Number(value) / total.value);
}
return 0;
});
const processList = computed(() => {
return [
{ name: 'Turnout', value: turnout.value, class: 'bg-info' },
{ name: 'Yes', value: yes.value, class: 'bg-success' },
{ name: 'No', value: no.value, class: 'bg-error' },
{ name: 'No With Veto', value: veto.value, class: 'bg-primary' },
{ name: 'Abstain', value: abstain.value, class: 'bg-warning' },
];
});
</script>
<template>
<div>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-col md:justify-between md:flex-row">
<p class="truncate w-full">{{ proposal_id }}. {{ proposal.content?.title }} </p>
<div
class="badge badge-ghost"
:class="
color === 'success'
? 'text-yes'
: color === 'error'
? 'text-no'
: 'text-info'
"
>{{ status }}</div>
</h2>
<div class="">
<ObjectElement :value="proposal.content"/>
<h2 class="card-title flex flex-col md:justify-between md:flex-row">
<p class="truncate w-full">
{{ proposal_id }}. {{ proposal.content?.title }}
</p>
<div
class="badge badge-ghost"
:class="
color === 'success'
? 'text-yes'
: color === 'error'
? 'text-no'
: 'text-info'
"
>
{{ status }}
</div>
</h2>
<div class="">
<ObjectElement :value="proposal.content" />
</div>
</div>
<!-- grid lg:grid-cols-3 auto-rows-max-->
<!-- flex-col lg:flex-row flex -->
<div class="gap-4 mb-4 grid lg:grid-cols-3 auto-rows-max ">
<!-- flex-1 -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow ">
<h2 class="card-title">Tally</h2>
<div v-for="(item,index) of processList" :key="index">
<label class="block">{{item.name }}</label>
<div class="h-6 w-full relative">
<div class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm" :class="`${item.class}`"></div>
<div class="absolute inset-x-0 inset-y-0 rounded-sm" :class="`${item.class}`" :style="`width: ${item.value}`"></div>
<p class="absolute inset-x-0 inset-y-0 text-center text-sm text-[#666] dark:text-[#eee] flex items-center justify-center">{{ item.value }}</p>
</div>
</div>
<div>
<label for="vote" class="btn btn-primary float-right btn-sm mx-1" @click="dialog.open('vote', {proposal_id})">Vote</label>
<label for="deposit" class="btn btn-primary float-right btn-sm mx-1" @click="dialog.open('deposit', {proposal_id})">Deposit</label>
</div>
<div class="gap-4 mb-4 grid lg:grid-cols-3 auto-rows-max">
<!-- flex-1 -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title mb-1">Tally</h2>
<div class="mb-1" v-for="(item, index) of processList" :key="index">
<label class="block text-sm mb-1">{{ item.name }}</label>
<div class="h-5 w-full relative">
<div
class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm"
:class="`${item.class}`"
></div>
<div
class="absolute inset-x-0 inset-y-0 rounded-sm"
:class="`${item.class}`"
:style="`width: ${item.value}`"
></div>
<p
class="absolute inset-x-0 inset-y-0 text-center text-sm text-[#666] dark:text-[#eee] flex items-center justify-center"
>
{{ item.value }}
</p>
</div>
</div>
<!-- lg:col-span-2 -->
<!-- lg:flex-[2_2_0%] -->
<div class="h-max bg-base-100 px-4 pt-3 pb-4 rounded shadow lg:col-span-2">
<h2 class="card-title">Timeline</h2>
<VTimeline
class="mt-2"
side="end"
align="start"
line-inset="8"
truncate-line="both"
density="compact"
>
<VTimelineItem
dot-color="error"
size="x-small"
>
<!-- 👉 Header -->
<div class="d-flex justify-space-between flex-wrap mb-3">
<h6 class="text-base font-weight-medium me-3">
Submited at: {{ format.toDay(proposal.submit_time) }}
</h6>
<small class="text-xs text-disabled my-1">{{ shortTime(proposal.submit_time) }}</small>
</div>
</VTimelineItem>
<VTimelineItem
size="x-small"
dot-color="primary"
>
<!-- 👉 Header -->
<div class="d-flex justify-space-between flex-wrap mb-3">
<h6 class="text-base font-weight-medium me-3">
Deposited at: {{ format.toDay(proposal.status==="PROPOSAL_STATUS_DEPOSIT_PERIOD"?proposal.deposit_end_time: proposal.voting_start_time) }}
</h6>
<small class="text-xs text-disabled text-no-wrap my-1">{{ shortTime(proposal.status==="PROPOSAL_STATUS_DEPOSIT_PERIOD"?proposal.deposit_end_time: proposal.voting_start_time) }}</small>
</div>
<p class="mb-0">
<div v-for="x of deposit.deposits">
{{ x.depositor }} {{ format.formatTokens(x.amount) }}
</div>
</p>
</VTimelineItem>
<VTimelineItem
size="x-small"
dot-color="success"
>
<!-- 👉 Header -->
<div class="d-flex justify-space-between flex-wrap mb-3">
<h6 class="text-base font-weight-medium me-3">
Voting start from {{ format.toDay(proposal.voting_start_time) }}
</h6>
<small class="text-xs text-disabled text-no-wrap my-1">{{ shortTime(proposal.voting_start_time) }}</small>
</div>
<!-- 👉 Content -->
<p class="mb-0">
<Countdown :time="votingCountdown"/>
</p>
</VTimelineItem>
<VTimelineItem
size="x-small"
dot-color="success"
>
<!-- 👉 Header -->
<div class="d-flex justify-space-between flex-wrap mb-3">
<h6 class="text-base font-weight-medium me-3">
Voting end {{ format.toDay(proposal.voting_end_time) }}
</h6>
<small class="text-xs text-disabled text-no-wrap my-1">{{ shortTime(proposal.voting_end_time) }}</small>
</div>
<!-- 👉 Content -->
<p class="mb-0">
Current Status: {{ proposal.status }}
</p>
</VTimelineItem>
<VTimelineItem
v-if="proposal.content && proposal.content['@type'].endsWith('SoftwareUpgradeProposal')"
size="x-small"
dot-color="success"
>
<!-- 👉 Header -->
<div class="d-flex justify-space-between flex-wrap mb-3">
<h6 class="text-base font-weight-medium me-3">
Upgrade Plan:
<span v-if="Number(proposal.content?.plan?.height||'0') > 0"> (EST)</span>
<span v-else>{{ format.toDay(proposal.content?.plan?.time) }}</span>
</h6>
<small class="text-xs text-disabled text-no-wrap my-1">{{ shortTime(proposal.voting_end_time) }}</small>
</div>
<!-- 👉 Content -->
<p class="mb-0">
<Countdown :time="upgradeCountdown"/>
</p>
</VTimelineItem>
</VTimeline>
<div class="mt-6 grid grid-cols-2">
<label
for="vote"
class="btn btn-primary float-right btn-sm mx-1"
@click="dialog.open('vote', { proposal_id })"
>Vote</label
>
<label
for="deposit"
class="btn btn-primary float-right btn-sm mx-1"
@click="dialog.open('deposit', { proposal_id })"
>Deposit</label
>
</div>
</div>
<div
class="bg-base-100 px-4 pt-3 pb-5 rounded shadow lg:col-span-2"
>
<h2 class="card-title">Timeline</h2>
<div class="px-1">
<div class="flex items-center mb-4 mt-2">
<div class="w-2 h-2 rounded-full bg-error mr-3"></div>
<div class="text-base flex-1 text-main">
Submited at: {{ format.toDay(proposal.submit_time) }}
</div>
<div class="text-sm">{{ shortTime(proposal.submit_time) }}</div>
</div>
<div class="flex items-center mb-4">
<div class="w-2 h-2 rounded-full bg-primary mr-3"></div>
<div class="text-base flex-1 text-main">
Deposited at:
{{
format.toDay(
proposal.status === 'PROPOSAL_STATUS_DEPOSIT_PERIOD'
? proposal.deposit_end_time
: proposal.voting_start_time
)
}}
</div>
<div class="text-sm">
{{
shortTime(
proposal.status === 'PROPOSAL_STATUS_DEPOSIT_PERIOD'
? proposal.deposit_end_time
: proposal.voting_start_time
)
}}
</div>
</div>
<div class="mb-4">
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-yes mr-3"></div>
<div class="text-base flex-1 text-main">
Voting start from {{ format.toDay(proposal.voting_start_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_start_time) }}
</div>
</div>
<div class="pl-5 text-sm mt-2">
<Countdown :time="votingCountdown" />
</div>
</div>
<div>
<div class="flex items-center mb-1">
<div class="w-2 h-2 rounded-full bg-success mr-3"></div>
<div class="text-base flex-1 text-main">
Voting end {{ format.toDay(proposal.voting_end_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
</div>
</div>
<div class="pl-5 text-sm">
Current Status: {{ proposal.status }}
</div>
</div>
<div
class="mt-4"
v-if="
proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')
"
>
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-warning mr-3"></div>
<div class="text-base flex-1 text-main">
Upgrade Plan:
<span v-if="Number(proposal.content?.plan?.height || '0') > 0">
(EST)</span
>
<span v-else>{{
format.toDay(proposal.content?.plan?.time)
}}</span>
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
</div>
</div>
<div class="pl-5 text-sm mt-2">
<Countdown :time="upgradeCountdown" />
</div>
</div>
</div>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title">Votes</h2>
<div class="overflow-x-auto">
<table class="table w-full">
<tbody>
<tr v-for="(item,index) of votes" :key="index">
<td>{{ item.voter }}</td>
<td>{{ item.option }}</td>
</tr>
</tbody>
</table>
<h2 class="card-title">Votes</h2>
<div class="overflow-x-auto">
<table class="table w-full table-zebra">
<tbody>
<tr v-for="(item, index) of votes" :key="index">
<td class="py-2 text-sm">{{ item.voter }}</td>
<td
class="py-2 text-sm"
:class="{
'text-yes': item.option === 'VOTE_OPTION_YES',
'text-gray-400': item.option === 'VOTE_OPTION_ABSTAIN',
}"
>
{{ item.option }}
</td>
</tr>
</tbody>
</table>
<button
v-if="votePage.next_key"
@click="loadMore()"
:disabled="loading"
class="btn btn-outline btn-primary w-full"
style="border: 1px solid hsl(var(--p));"
>Load more</button>
</div>
<button
@click="loadMore()"
v-if="votePage.next_key"
:disabled="loading"
class="btn btn-outline btn-primary w-full mt-4"
>
Load more
</button>
</div>
</div>
</div>
</div>
</template>
+1 -1
View File
@@ -7,7 +7,7 @@ const store = useGovStore();
onMounted(() => {
store.fetchProposals('2').then((x) => {
if (x.proposals.length === 0) {
if (x?.proposals?.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
+1 -1
View File
@@ -106,7 +106,7 @@ function color(v: string) {
<table class="table w-full mt-4">
<thead>
<tr>
<th style="position: relative">Channel Id</th>
<th style="position: relative; z-index: 2;">Channel Id</th>
<th>Port Id</th>
<th>Counterparty</th>
<th>Hops</th>
+1 -1
View File
@@ -28,7 +28,7 @@ function color(v: string) {
<table class="table w-full">
<thead>
<tr>
<th style="position: relative">Connection Id</th>
<th style="position: relative; z-index: 2;">Connection Id</th>
<th>Connection</th>
<th>Delay Period</th>
<th>State</th>
+5 -5
View File
@@ -146,14 +146,14 @@ export const useIndexModule = defineStore('module-index', {
title: 'Height',
color: 'primary',
icon: 'mdi-pound',
stats: String(base.latest.block?.header?.height || 0),
stats: String(base?.latest?.block?.header?.height || 0),
change: 0,
},
{
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base.latest.block?.last_commit?.signatures.length || 0),
stats: String(base?.latest?.block?.last_commit?.signatures.length || 0),
change: 0,
},
{
@@ -202,9 +202,9 @@ export const useIndexModule = defineStore('module-index', {
useDistributionStore()
.fetchCommunityPool()
.then((x) => {
this.communityPool = x.pool
.filter((t) => t.denom.length < 10)
.map((t) => ({
this.communityPool = x?.pool
?.filter((t) => t.denom.length < 10)
?.map((t) => ({
amount: String(parseInt(t.amount)),
denom: t.denom,
}));
+76 -71
View File
@@ -136,7 +136,7 @@ onMounted(() => {
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow border-indigo-500">
<div class="flex flex-col lg:flex-row">
<div class="flex flex-col lg:flex-row pt-2 pb-1">
<div class="flex-1">
<div class="flex">
<div class="avatar mr-4 relative w-24 rounded-lg overflow-hidden">
@@ -158,7 +158,7 @@ onMounted(() => {
</div>
<div class="mx-2">
<h4>{{ v.description?.moniker }}</h4>
<div class="text-sm mb-2">
<div class="text-sm mb-4">
{{ v.description?.identity || '-' }}
</div>
<label
@@ -174,41 +174,46 @@ onMounted(() => {
</div>
</div>
<div class="m-4 text-sm">
<p class="text-md">About Us</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-web">
<span>Website: </span
><span> {{ v.description?.website || '-' }}</span>
</VListItem>
<VListItem prepend-icon="mdi-email-outline">
<span>Contact: </span
><span> {{ v.description?.security_contact }}</span>
</VListItem>
</VList>
<p class="text-md mt-3">Validator Status</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-shield-account-outline">
<p class="text-sm mb-3">About Us</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-web" class="text-xl mr-1" />
<span>Website: </span>
<span> {{ v.description?.website || '-' }}</span>
</div>
<div class="flex items-center">
<Icon icon="mdi-email-outline" class="text-xl mr-1" />
<span>Contact: </span>
<span> {{ v.description?.security_contact }}</span>
</div>
</div>
<p class="text-sm mt-4 mb-3">Validator Status</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-shield-account-outline" class="text-xl mr-1" />
<span>Status: </span
><span>
{{ String(v.status).replace('BOND_STATUS_', '') }}
</span>
</VListItem>
<VListItem prepend-icon="mdi-shield-alert-outline">
<span>Jailed: </span><span> {{ v.jailed || '-' }} </span>
</VListItem>
</VList>
</div>
<div class="flex items-center">
<Icon icon="mdi-shield-alert-outline" class="text-xl mr-1" />
<span>Jailed: </span>
<span> {{ v.jailed || '-' }} </span>
</div>
</div>
</div>
</div>
<div class="flex-1">
<div class="d-flex flex-column py-3 justify-space-between">
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-coin"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<div class="flex flex-col justify-between">
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-coin" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>
{{
format.formatToken2({
@@ -220,14 +225,14 @@ onMounted(() => {
<span class="text-sm">Total Bonded Tokens</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-percent"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-percent" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>
{{ format.formatToken(selfBonded.balance) }} ({{ selfRate }})
</h4>
@@ -235,13 +240,14 @@ onMounted(() => {
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-account-tie"
></VAvatar>
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-account-tie" class="text-3xl" />
</div>
<div class="ml-3 d-flex flex-column justify-center">
<h4>
{{ v.min_self_delegation }} {{ staking.params.bond_denom }}
@@ -249,40 +255,40 @@ onMounted(() => {
<span class="text-sm">Min Self Delegation:</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-finance"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-finance" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ apr }}</h4>
<span class="text-sm">Annual Profit</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-stairs-up"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-stairs-up" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ v.unbonding_height }}</h4>
<span class="text-sm">Unbonding Height</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-clock"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<div class="flex mb-2">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-clock" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ format.toDay(v.unbonding_time, 'from') }}</h4>
<span class="text-sm">Unbonding Time</span>
</div>
@@ -290,8 +296,7 @@ onMounted(() => {
</div>
</div>
</div>
<div class="divider"></div>
<div class="text-sm px-4">{{ v.description?.details }}</div>
<div class="text-sm px-4 pt-3 border-t">{{ v.description?.details }}</div>
</div>
<div class="mt-3 grid grid-cols-1 md:grid-cols-3 gap-4">
+1 -1
View File
@@ -49,7 +49,7 @@ const messages = computed(() => {
<tr>
<td>Status</td>
<td>
<VChip color="primary">oioio</VChip>
<div class="badge badge-primary badge-sm">oioio</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit mr-2"
:class="`text-${
+7 -4
View File
@@ -106,12 +106,15 @@ watchEffect(() => {
class="input input-sm w-full flex-1"
/>
<button class="btn btn-primary btn-sm">
<Icon icon="mdi-star" class="mr-2 text-lg" /> <span class="">Favorite</span>
<Icon icon="mdi-star" class="mr-2 text-lg" />
<span class="">Favorite</span>
</button>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 mt-4">
<div v-for="(v, i) in validators">
<div
class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-x-4 mt-4"
>
<div v-for="(v, i) in validators" :key="i">
<div class="flex items-center justify-between">
<VCheckbox
v-model="selected"
@@ -119,7 +122,7 @@ watchEffect(() => {
:value="v.operator_address"
>
<template v-slot:label>
<span class="text-truncate"
<span class="text-truncate text-sm"
>{{ i + 1 }}. {{ v.description.moniker }}</span
>
</template>