feat: input border

This commit is contained in:
Alisa | Side.one 2023-05-28 09:35:08 +08:00
parent 2f93b4c10c
commit f10e4983b4
4 changed files with 464 additions and 343 deletions

View File

@ -35,7 +35,7 @@
"md-editor-v3": "^2.8.1", "md-editor-v3": "^2.8.1",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"osmojs": "^14.0.0-rc.0", "osmojs": "^14.0.0-rc.0",
"ping-widget": "^0.0.28", "ping-widget": "^0.0.29",
"pinia": "^2.0.28", "pinia": "^2.0.28",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",

View File

@ -3,7 +3,7 @@ import {
useBlockchain, useBlockchain,
useFormatter, useFormatter,
useStakingStore, useStakingStore,
useTxDialog useTxDialog,
} from '@/stores'; } from '@/stores';
import { select } from '@/components/dynamic/index'; import { select } from '@/components/dynamic/index';
import type { PaginatedProposals } from '@/types'; import type { PaginatedProposals } from '@/types';
@ -45,55 +45,94 @@ const proposalInfo = ref();
<tbody> <tbody>
<tr v-for="(item, index) in proposals?.proposals" :key="index"> <tr v-for="(item, index) in proposals?.proposals" :key="index">
<td class="px-4 w-20"> <td class="px-4 w-20">
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer" <label
@click="proposalInfo = item"> for="proposal-detail-modal"
#{{ item?.proposal_id }}</label> class="text-main text-base hover:text-indigo-400 cursor-pointer"
@click="proposalInfo = item"
>
#{{ item?.proposal_id }}</label
>
</td> </td>
<td class="w-full"> <td class="w-full">
<div> <div>
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`" <RouterLink
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"> :to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
>
{{ item?.content?.title }} {{ item?.content?.title }}
</RouterLink> </RouterLink>
<div <div
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"> class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
>
{{ showType(item.content['@type']) }} {{ showType(item.content['@type']) }}
</div> </div>
</div> </div>
</td> </td>
<td class="w-60"> <td class="w-60">
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess> <ProposalProcess
:pool="staking.pool"
:tally="item.final_tally_result"
></ProposalProcess>
</td> </td>
<td class="w-36"> <td class="w-36">
<div class="pl-4"> <div class="pl-4">
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED' <div
? 'text-yes' class="flex items-center"
: statusMap?.[item?.status] === 'REJECTED' :class="
? 'text-no' statusMap?.[item?.status] === 'PASSED'
: 'text-info' ? 'text-yes'
"> : statusMap?.[item?.status] ===
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED' 'REJECTED'
? 'bg-yes' ? 'text-no'
: statusMap?.[item?.status] === 'REJECTED' : 'text-info'
? 'bg-no' "
: 'bg-info' >
"></div> <div
class="w-1 h-1 rounded-full mr-2"
:class="
statusMap?.[item?.status] === 'PASSED'
? 'bg-yes'
: statusMap?.[item?.status] ===
'REJECTED'
? 'bg-no'
: 'bg-info'
"
></div>
<div class="text-xs"> <div class="text-xs">
{{ statusMap?.[item?.status] || item?.status }} {{
statusMap?.[item?.status] ||
item?.status
}}
</div> </div>
</div> </div>
<div <div
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start"> class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start"
>
{{ format.toDay(item.voting_end_time, 'from') }} {{ format.toDay(item.voting_end_time, 'from') }}
</div> </div>
</div> </div>
</td> </td>
<td v-if="statusMap?.[item?.status] === 'VOTING'" class="w-40"> <td
v-if="statusMap?.[item?.status] === 'VOTING'"
class="w-40"
>
<div class=""> <div class="">
<label for="vote" class="btn btn-xs btn-primary rounded" <label
@click="dialog.open('vote', { proposal_id: item?.proposal_id })"> for="vote"
<span v-if="item?.voterStatus">{{ item?.voterStatus.replace("VOTE_OPTION_", "") }}</span> class="btn btn-xs btn-primary rounded-sm"
@click="
dialog.open('vote', {
proposal_id: item?.proposal_id,
})
"
>
<span v-if="item?.voterStatus">{{
item?.voterStatus.replace(
'VOTE_OPTION_',
''
)
}}</span>
<span v-else>Vote</span> <span v-else>Vote</span>
</label> </label>
</div> </div>
@ -103,56 +142,99 @@ const proposalInfo = ref();
</table> </table>
<div class="lg:!hidden"> <div class="lg:!hidden">
<div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4"> <div
<div class="text-main text-base mb-1 flex justify-between hover:text-indigo-400"> v-for="(item, index) in proposals?.proposals"
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`" class="flex-1 w-0 truncate mr-4">{{ :key="index"
item?.content?.title }}</RouterLink> class="px-4 py-4"
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer" >
@click="proposalInfo = item"> <div
#{{ item?.proposal_id }}</label> class="text-main text-base mb-1 flex justify-between hover:text-indigo-400"
>
<RouterLink
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="flex-1 w-0 truncate mr-4"
>{{ item?.content?.title }}</RouterLink
>
<label
for="proposal-detail-modal"
class="text-main text-base hover:text-indigo-400 cursor-pointer"
@click="proposalInfo = item"
>
#{{ item?.proposal_id }}</label
>
</div> </div>
<div class="grid grid-cols-4 mt-2 mb-2"> <div class="grid grid-cols-4 mt-2 mb-2">
<div class="col-span-2"> <div class="col-span-2">
<div <div
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"> class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
>
{{ showType(item.content['@type']) }} {{ showType(item.content['@type']) }}
</div> </div>
</div> </div>
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED' <div
? 'text-yes' class="flex items-center"
: statusMap?.[item?.status] === 'REJECTED' :class="
? 'text-no' statusMap?.[item?.status] === 'PASSED'
: 'text-info' ? 'text-yes'
"> : statusMap?.[item?.status] === 'REJECTED'
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED' ? 'text-no'
? 'bg-yes' : 'text-info'
: statusMap?.[item?.status] === 'REJECTED' "
? 'bg-no' >
: 'bg-info' <div
"></div> class="w-1 h-1 rounded-full mr-2"
:class="
statusMap?.[item?.status] === 'PASSED'
? 'bg-yes'
: statusMap?.[item?.status] === 'REJECTED'
? 'bg-no'
: 'bg-info'
"
></div>
<div class="text-xs flex items-center"> <div class="text-xs flex items-center">
{{ statusMap?.[item?.status] || item?.status }} {{ statusMap?.[item?.status] || item?.status }}
</div> </div>
</div> </div>
<div class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"> <div
class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"
>
{{ format.toDay(item.voting_end_time, 'from') }} {{ format.toDay(item.voting_end_time, 'from') }}
</div> </div>
</div> </div>
<div> <div>
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess> <ProposalProcess
:pool="staking.pool"
:tally="item.final_tally_result"
></ProposalProcess>
</div> </div>
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'"> <div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
<div class="" v-show="item?.voterStatus === 'No With Veto'"> <div class="" v-show="item?.voterStatus === 'No With Veto'">
<label for="vote" class="btn btn-xs btn-primary rounded" <label
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">Vote</label> for="vote"
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit" class="btn btn-xs btn-primary rounded-sm"
:class="`text-${voterStatusMap?.[item?.voterStatus]}`" @click="
v-show="item?.voterStatus !== 'No With Veto'"> dialog.open('vote', {
<span class="inset-x-0 inset-y-0 opacity-10 absolute" proposal_id: item?.proposal_id,
:class="`bg-${voterStatusMap?.[item?.voterStatus]}`"></span> })
"
>Vote</label
>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit"
:class="`text-${
voterStatusMap?.[item?.voterStatus]
}`"
v-show="item?.voterStatus !== 'No With Veto'"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${
voterStatusMap?.[item?.voterStatus]
}`"
></span>
{{ item?.voterStatus }} {{ item?.voterStatus }}
</div> </div>
</div> </div>
@ -160,15 +242,30 @@ const proposalInfo = ref();
</div> </div>
</div> </div>
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" /> <input
type="checkbox"
id="proposal-detail-modal"
class="modal-toggle"
/>
<label for="proposal-detail-modal" class="modal"> <label for="proposal-detail-modal" class="modal">
<label class="modal-box w-11/12 max-w-5xl" for=""> <label class="modal-box w-11/12 max-w-5xl" for="">
<label for="proposal-detail-modal" class="btn btn-sm btn-circle absolute right-2 top-2"></label> <label
for="proposal-detail-modal"
class="btn btn-sm btn-circle absolute right-2 top-2"
></label
>
<h3 class="font-bold text-lg">Description</h3> <h3 class="font-bold text-lg">Description</h3>
<p class="py-4"> <p class="py-4">
<Component v-if="proposalInfo?.content?.description" <Component
:is="select(proposalInfo?.content?.description, 'horizontal')" v-if="proposalInfo?.content?.description"
:value="proposalInfo?.content?.description"> :is="
select(
proposalInfo?.content?.description,
'horizontal'
)
"
:value="proposalInfo?.content?.description"
>
</Component> </Component>
</p> </p>
</label> </label>

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
useBaseStore, useBaseStore,
useFormatter, useFormatter,
useStakingStore, useStakingStore,
useTxDialog, useTxDialog,
} from '@/stores'; } from '@/stores';
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
import { onMounted, ref, type DebuggerEvent } from 'vue'; import { onMounted, ref, type DebuggerEvent } from 'vue';
@ -22,319 +22,343 @@ const tab = ref('active');
const unbondList = ref([] as Validator[]); const unbondList = ref([] as Validator[]);
const base = useBaseStore(); const base = useBaseStore();
onMounted(() => { onMounted(() => {
staking.fetchInacitveValdiators().then((x) => { staking.fetchInacitveValdiators().then((x) => {
unbondList.value = x; unbondList.value = x;
}); });
}); });
async function fetchChange() { async function fetchChange() {
let page = 0; let page = 0;
let height = Number(base.latest?.block?.header?.height || 0); let height = Number(base.latest?.block?.header?.height || 0);
if (height > 14400) { if (height > 14400) {
height -= 14400; height -= 14400;
} else { } else {
height = 1; height = 1;
} }
// voting power in 24h ago // voting power in 24h ago
while (page < staking.validators.length && height > 0) { while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => { await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => { x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power); yesterday.value[v.pub_key.key] = Number(v.voting_power);
}); });
}); });
page += 100; page += 100;
} }
page = 0; page = 0;
// voting power for now // voting power for now
while (page < staking.validators.length) { while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => { await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => { x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power); latest.value[v.pub_key.key] = Number(v.voting_power);
}); });
}); });
page += 100; page += 100;
} }
} }
const changes = computed(() => { const changes = computed(() => {
const changes = {} as Record<string, number>; const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => { Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0; const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0; const y = yesterday.value[k] || 0;
changes[k] = l - y; changes[k] = l - y;
}); });
return changes; return changes;
}); });
const change24 = (key: Key) => { const change24 = (key: Key) => {
const txt = key.key; const txt = key.key;
// const n: number = latest.value[txt]; // const n: number = latest.value[txt];
// const o: number = yesterday.value[txt]; // const o: number = yesterday.value[txt];
// // console.log( txt, n, o) // // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0; // return n > 0 && o > 0 ? n - o : 0;
return changes.value[txt]; return changes.value[txt];
}; };
const change24Text = (key?: Key) => { const change24Text = (key?: Key) => {
if (!key) return ''; if (!key) return '';
const v = change24(key); const v = change24(key);
return v && v !== 0 ? format.showChanges(v) : ''; return v && v !== 0 ? format.showChanges(v) : '';
}; };
const change24Color = (key?: Key) => { const change24Color = (key?: Key) => {
if (!key) return ''; if (!key) return '';
const v = change24(key); const v = change24(key);
if (v > 0) return 'text-success'; if (v > 0) return 'text-success';
if (v < 0) return 'text-error'; if (v < 0) return 'text-error';
}; };
const list = computed(() => { const list = computed(() => {
return tab.value === 'active' ? staking.validators : unbondList.value; return tab.value === 'active' ? staking.validators : unbondList.value;
// return staking.validators // return staking.validators
}); });
const loadAvatars = () => { const loadAvatars = () => {
// fetch avatar from keybase // fetch avatar from keybase
let promise = Promise.resolve(); let promise = Promise.resolve();
staking.validators.forEach((item) => { staking.validators.forEach((item) => {
promise = promise.then( promise = promise.then(
() => () =>
new Promise((resolve) => { new Promise((resolve) => {
const identity = item.description?.identity; const identity = item.description?.identity;
if (identity && !avatars.value[identity]) { if (identity && !avatars.value[identity]) {
staking.keybase(identity).then((d) => { staking.keybase(identity).then((d) => {
if (Array.isArray(d.them) && d.them.length > 0) { if (Array.isArray(d.them) && d.them.length > 0) {
const uri = String(d.them[0]?.pictures?.primary?.url).replace( const uri = String(
'https://s3.amazonaws.com/keybase_processed_uploads/', d.them[0]?.pictures?.primary?.url
'' ).replace(
); 'https://s3.amazonaws.com/keybase_processed_uploads/',
if (uri) { ''
avatars.value[identity] = uri; );
localStorage.setItem( if (uri) {
'avatars', avatars.value[identity] = uri;
JSON.stringify(avatars.value) localStorage.setItem(
); 'avatars',
} JSON.stringify(avatars.value)
} );
resolve(); }
}); }
} else { resolve();
resolve(); });
} } else {
}) resolve();
); }
}); })
);
});
}; };
const logo = (identity?: string) => { const logo = (identity?: string) => {
if (!identity) return ''; if (!identity) return '';
const url = avatars.value[identity] || ''; const url = avatars.value[identity] || '';
return url.startsWith('http') return url.startsWith('http')
? url ? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`; : `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
}; };
const rank = function (position: number) { const rank = function (position: number) {
let sum = 0; let sum = 0;
for (let i = 0; i < position; i++) { for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares); sum += Number(staking.validators[i]?.delegator_shares);
} }
const percent = sum / staking.totalPower; const percent = sum / staking.totalPower;
switch (true) { switch (true) {
case tab.value === 'active' && percent < 0.33: case tab.value === 'active' && percent < 0.33:
return 'error'; return 'error';
case tab.value === 'active' && percent < 0.67: case tab.value === 'active' && percent < 0.67:
return 'warning'; return 'warning';
default: default:
return 'primary'; return 'primary';
} }
}; };
fetchChange(); fetchChange();
loadAvatars(); loadAvatars();
</script> </script>
<template> <template>
<div> <div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="tabs tabs-boxed bg-transparent mb-4"> <div class="tabs tabs-boxed bg-transparent mb-4">
<a <a
class="tab text-gray-400" class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }" :class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'" @click="tab = 'active'"
>Active</a >Active</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>Inactive</a
>
</div>
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead>
<tr>
<th scope="col" style="width: 3rem; position: relative">#</th>
<th scope="col">VALIDATOR</th>
<th scope="col" class="text-right">VOTING POWER</th>
<th scope="col" class="text-right">24h CHANGES</th>
<th scope="col" class="text-right">COMMISSION</th>
<th scope="col" class="text-center">ACTIONS</th>
</tr>
</thead>
<tbody>
<tr
v-for="(v, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank(i)}`"
> >
<span <a
class="inset-x-0 inset-y-0 opacity-10 absolute" class="tab text-gray-400"
:class="`bg-${rank(i)}`" :class="{ 'tab-active': tab === 'inactive' }"
></span> @click="tab = 'inactive'"
{{ i + 1 }} >Inactive</a
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 400px"
> >
<div </div>
class="avatar mr-4 relative w-8 h-8 rounded-full overflow-hidden"
>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="v.description?.identity"
v-lazy="logo(v.description?.identity)"
class="object-contain"
/>
<Icon
v-else
class="text-4xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col"> <div class="text-lg font-semibold">
<h6 class="text-sm text-primary"> {{ list.length }}/{{ staking.params.max_validators }}
<RouterLink </div>
:to="{
name: 'chain-staking-validator',
params: { validator: v.operator_address },
}"
class="font-weight-medium user-list-name"
>
{{ v.description?.moniker }}
</RouterLink>
</h6>
<span class="text-xs">{{
v.description?.website || v.description?.identity || '-'
}}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium">
{{
format.formatToken(
{
amount: parseInt(v.tokens).toString(),
denom: staking.params.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v.consensus_pubkey)"
>
{{ change24Text(v.consensus_pubkey) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div v-if="v.jailed" class="badge badge-error gap-2 text-white">
Jailed
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address: v.operator_address,
})
"
>Delegate</label>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"></span>
Top 33%
</div> </div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning" <div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
> <div class="overflow-x-auto">
<span <table class="table staking-table w-full">
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning" <thead>
></span> <tr>
Top 67% <th
scope="col"
style="width: 3rem; position: relative"
>
#
</th>
<th scope="col">VALIDATOR</th>
<th scope="col" class="text-right">VOTING POWER</th>
<th scope="col" class="text-right">24h CHANGES</th>
<th scope="col" class="text-right">COMMISSION</th>
<th scope="col" class="text-center">ACTIONS</th>
</tr>
</thead>
<tbody>
<tr
v-for="(v, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank(i)}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank(i)}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 400px"
>
<div
class="avatar mr-4 relative w-8 h-8 rounded-full overflow-hidden"
>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="v.description?.identity"
v-lazy="
logo(
v.description?.identity
)
"
class="object-contain"
/>
<Icon
v-else
class="text-4xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col">
<h6 class="text-sm text-primary">
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator:
v.operator_address,
},
}"
class="font-weight-medium user-list-name"
>
{{ v.description?.moniker }}
</RouterLink>
</h6>
<span class="text-xs">{{
v.description?.website ||
v.description?.identity ||
'-'
}}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium">
{{
format.formatToken(
{
amount: parseInt(
v.tokens
).toString(),
denom: staking.params
.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v.consensus_pubkey)"
>
{{ change24Text(v.consensus_pubkey) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
Jailed
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address:
v.operator_address,
})
"
>Delegate</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
Top 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
Top 67%
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<route> <route>
@ -348,7 +372,7 @@ loadAvatars();
<style> <style>
.staking-table.table :where(th, td) { .staking-table.table :where(th, td) {
padding: 8px 5px; padding: 8px 5px;
background: transparent; background: transparent;
} }
</style> </style>

View File

@ -5476,10 +5476,10 @@ pify@^3.0.0:
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
ping-widget@^0.0.28: ping-widget@^0.0.29:
version "0.0.28" version "0.0.29"
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.28.tgz#f232284556c3527e874b8d410f5e7fede8845c11" resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.29.tgz#c1b251e381677360c517fc71a9f3b78b1444c417"
integrity sha512-iCitfqYSfT/BqP83UpMr9qQrd/ukwMfJDVtIaupj9e5l+BhSc9XHhzg5URpH4wRlNvhr3jqmAVuq0KZASX6Hsw== integrity sha512-gqKQuqPpo1cKkqE4EOlVleW9LKefjwtYr7nl/uQdiLjwAlRvJt7dA5fddfyEQaTk1CrdARS9aj+VrHiZx+SxkA==
dependencies: dependencies:
"@cosmjs/amino" "^0.30.1" "@cosmjs/amino" "^0.30.1"
"@cosmjs/cosmwasm-stargate" "^0.30.1" "@cosmjs/cosmwasm-stargate" "^0.30.1"