forked from LaconicNetwork/cosmos-explorer
Merge pull request #372 from alisaweb3/v3-single
UI Refactor: language switch, theme toggle,governance,block,staking
This commit is contained in:
commit
8da4118f31
@ -25,7 +25,7 @@ function calculateValue(value: any){
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="bg-card px-4 pt-3 pb-4 rounded mt-5"
|
class="bg-base-100 px-4 pt-3 pb-4 rounded mt-5"
|
||||||
v-if="props.cardItem?.items && props.cardItem?.items?.length > 0"
|
v-if="props.cardItem?.items && props.cardItem?.items?.length > 0"
|
||||||
>
|
>
|
||||||
<div class="text-base mb-3 text-main">{{ props.cardItem?.title }}</div>
|
<div class="text-base mb-3 text-main">{{ props.cardItem?.title }}</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getLogo, useDashboard, } from '@/stores/useDashboard';
|
import { getLogo, useDashboard } from '@/stores/useDashboard';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: {
|
name: {
|
||||||
@ -10,27 +10,40 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dashboardStore = useDashboard()
|
const dashboardStore = useDashboard();
|
||||||
const conf = computed(() => dashboardStore.chains[props.name] || {})
|
const conf = computed(() => dashboardStore.chains[props.name] || {});
|
||||||
|
|
||||||
const addFavor = (e: Event) => {
|
const addFavor = (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dashboardStore.favoriteMap[props.name] = !dashboardStore?.favoriteMap?.[props.name];
|
dashboardStore.favoriteMap[props.name] =
|
||||||
window.localStorage.setItem('favoriteMap', JSON.stringify(dashboardStore.favoriteMap))
|
!dashboardStore?.favoriteMap?.[props.name];
|
||||||
}
|
window.localStorage.setItem(
|
||||||
|
'favoriteMap',
|
||||||
|
JSON.stringify(dashboardStore.favoriteMap)
|
||||||
|
);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<RouterLink :to="`/${name}`" class="bg-base-100 rounded shadow flex items-center px-3 py-3 cursor-pointer">
|
<RouterLink
|
||||||
|
:to="`/${name}`"
|
||||||
|
class="bg-base-100 hover:bg-base-content rounded shadow flex items-center px-3 py-3 cursor-pointer"
|
||||||
|
>
|
||||||
<div class="w-8 h-8 rounded-full overflow-hidden">
|
<div class="w-8 h-8 rounded-full overflow-hidden">
|
||||||
<img :src="conf.logo" />
|
<img :src="conf.logo" />
|
||||||
</div>
|
</div>
|
||||||
<div class="font-semibold ml-4 text-base flex-1">
|
<div class="font-semibold ml-4 text-base flex-1">
|
||||||
{{ conf?.prettyName || props.name }}
|
{{ conf?.prettyName || props.name }}
|
||||||
</div>
|
</div>
|
||||||
<div @click="addFavor" class="pl-4 text-xl"
|
<div
|
||||||
:class="{ 'text-warning': dashboardStore?.favoriteMap?.[props.name], 'text-gray-300 dark:text-gray-500': !dashboardStore?.favoriteMap?.[props.name] }">
|
@click="addFavor"
|
||||||
|
class="pl-4 text-xl"
|
||||||
|
:class="{
|
||||||
|
'text-warning': dashboardStore?.favoriteMap?.[props.name],
|
||||||
|
'text-gray-300 dark:text-gray-500':
|
||||||
|
!dashboardStore?.favoriteMap?.[props.name],
|
||||||
|
}"
|
||||||
|
>
|
||||||
<Icon icon="mdi-star" />
|
<Icon icon="mdi-star" />
|
||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useThemeConfig } from '@core/composable/useThemeConfig';
|
import { useThemeConfig } from '@core/composable/useThemeConfig';
|
||||||
import type { ThemeSwitcherTheme } from '@layouts/types';
|
import type { ThemeSwitcherTheme } from '@layouts/types';
|
||||||
|
import { Icon } from '@iconify/vue';
|
||||||
import { onMounted, watch } from 'vue';
|
import { onMounted, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -50,10 +51,12 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<IconBtn @click="changeTheme">
|
<div class="tooltip tooltip-bottom delay-1000" :data-tip="currentThemeName">
|
||||||
<VIcon :icon="props.themes[currentThemeIndex].icon" />
|
<button
|
||||||
<VTooltip activator="parent" open-delay="1000">
|
class="btn btn-ghost btn-circle btn-sm mx-1"
|
||||||
<span class="text-capitalize">{{ currentThemeName }}</span>
|
@click="changeTheme"
|
||||||
</VTooltip>
|
>
|
||||||
</IconBtn>
|
<Icon :icon="props.themes[currentThemeIndex].icon" class="text-2xl" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -6,14 +6,18 @@ const props = defineProps(["value"]);
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VTable>
|
<div class="overflow-x-auto">
|
||||||
<tbody>
|
<table class="table w-full text-sm">
|
||||||
<tr v-for="(v, k) of value">
|
<tbody>
|
||||||
<td class="text-capitalize" style="max-width: 200px;">{{ k }}</td>
|
<tr v-for="(v, k) of value">
|
||||||
<td><div class="overflow-hidden w-auto" style="max-width: 1000px;">
|
<td class="text-capitalize" style="max-width: 200px;">{{ k }}</td>
|
||||||
<Component v-if="v" :is="select(v, 'horizontal')" :value="v"></Component></div>
|
<td>
|
||||||
</td>
|
<div class="overflow-hidden w-auto whitespace-normal" style="max-width: 1000px;">
|
||||||
</tr>
|
<Component v-if="v" :is="select(v, 'horizontal')" :value="v"></Component>
|
||||||
</tbody>
|
</div>
|
||||||
</VTable>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="h-100 d-flex align-center justify-space-between">
|
|
||||||
<!-- 👉 Footer: left content -->
|
|
||||||
<span class="d-flex align-center">
|
|
||||||
©
|
|
||||||
{{ new Date().getFullYear() }}
|
|
||||||
Made With
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-heart-outline"
|
|
||||||
color="error"
|
|
||||||
size="1.25rem"
|
|
||||||
class="mx-1"
|
|
||||||
/>
|
|
||||||
By <a
|
|
||||||
href="https://ping.pub"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="text-primary ms-1"
|
|
||||||
>Ping.pub</a>
|
|
||||||
</span>
|
|
||||||
<!-- 👉 Footer: right content -->
|
|
||||||
<span class="d-md-flex gap-x-4 text-primary d-none">
|
|
||||||
<a
|
|
||||||
href="https://github.com/ping-pub/explorer/blob/master/LICENSE"
|
|
||||||
target="noopener noreferrer"
|
|
||||||
>License</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/ping-pub/explorer"
|
|
||||||
target="noopener noreferrer"
|
|
||||||
>Github</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -20,6 +20,5 @@ const themes: ThemeSwitcherTheme[] = [
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NewThemeSwitcher :themes="themes"/>
|
<NewThemeSwitcher :themes="themes"/>
|
||||||
<!-- <ThemeSwitcher :themes="themes" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,58 +1,66 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import TxsElement from '@/components/dynamic/TxsElement.vue';
|
import TxsElement from '@/components/dynamic/TxsElement.vue';
|
||||||
import { useBlockModule } from './block'
|
import { useBlockModule } from './block';
|
||||||
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
||||||
import { computed } from '@vue/reactivity';
|
import { computed } from '@vue/reactivity';
|
||||||
import { onBeforeRouteUpdate } from 'vue-router';
|
import { onBeforeRouteUpdate } from 'vue-router';
|
||||||
const props = defineProps(["height", "chain"]);
|
const props = defineProps(['height', 'chain']);
|
||||||
|
|
||||||
const store = useBlockModule()
|
const store = useBlockModule();
|
||||||
store.fetchBlock(props.height)
|
store.fetchBlock(props.height);
|
||||||
const tab = ref('summary')
|
const tab = ref('summary');
|
||||||
|
|
||||||
const height = computed(() => {
|
const height = computed(() => {
|
||||||
return Number(store.current.block?.header?.height || props.height || 0)
|
return Number(store.current.block?.header?.height || props.height || 0);
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeRouteUpdate(async (to, from, next) => {
|
onBeforeRouteUpdate(async (to, from, next) => {
|
||||||
if (from.path !== to.path) {
|
if (from.path !== to.path) {
|
||||||
store.fetchBlock(String(to.params.height))
|
store.fetchBlock(String(to.params.height));
|
||||||
next()
|
next();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard>
|
<VCard>
|
||||||
<VCardTitle class="d-flex justify-space-between">
|
<VCardTitle class="d-flex justify-space-between">
|
||||||
<span class="mt-2">#{{ store.current.block?.header?.height }}</span>
|
<span class="mt-2">#{{ store.current.block?.header?.height }}</span>
|
||||||
<span v-if="props.height" class="mt-2">
|
<span v-if="props.height" class="mt-2">
|
||||||
<VBtn size="32" :to="`/${store.blockchain.chainName}/block/${height - 1}`" class="mr-2"><VIcon icon="mdi-arrow-left"/></VBtn>
|
<VBtn
|
||||||
<VBtn size="32" :to="`/${store.blockchain.chainName}/block/${height + 1}`"><VIcon icon="mdi-arrow-right"/></VBtn>
|
size="32"
|
||||||
</span>
|
:to="`/${store.blockchain.chainName}/block/${height - 1}`"
|
||||||
</VCardTitle>
|
class="mr-2"
|
||||||
<VCardItem class="pt-0">
|
><VIcon icon="mdi-arrow-left"
|
||||||
<DynamicComponent :value="store.current.block_id"/>
|
/></VBtn>
|
||||||
</VCardItem>
|
<VBtn
|
||||||
|
size="32"
|
||||||
|
:to="`/${store.blockchain.chainName}/block/${height + 1}`"
|
||||||
|
><VIcon icon="mdi-arrow-right"
|
||||||
|
/></VBtn>
|
||||||
|
</span>
|
||||||
|
</VCardTitle>
|
||||||
|
<VCardItem class="pt-0">
|
||||||
|
<DynamicComponent :value="store.current.block_id" />
|
||||||
|
</VCardItem>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<VCard title="Block Header" class="my-5">
|
<VCard title="Block Header" class="my-5">
|
||||||
<VCardItem class="pt-0">
|
<VCardItem class="pt-0">
|
||||||
<DynamicComponent :value="store.current.block?.header"/>
|
<DynamicComponent :value="store.current.block?.header" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<VCard title="Transactions">
|
<VCard title="Transactions">
|
||||||
<VCardItem class="pt-0">
|
<VCardItem class="pt-0">
|
||||||
<TxsElement :value="store.current.block?.data?.txs"/>
|
<TxsElement :value="store.current.block?.data?.txs" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<VCard title="Last Commit" class="mt-5">
|
<VCard title="Last Commit" class="mt-5">
|
||||||
<VCardItem class="pt-0">
|
<VCardItem class="pt-0">
|
||||||
<DynamicComponent :value="store.current.block?.last_commit"/>
|
<DynamicComponent :value="store.current.block?.last_commit" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -11,76 +11,82 @@ const tab = ref('blocks');
|
|||||||
const format = useFormatter();
|
const format = useFormatter();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VCard>
|
<div>
|
||||||
<VCardTitle class="d-flex justify-space-between">
|
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||||
<VTabs v-model="tab">
|
<a
|
||||||
<VTab value="blocks">Blocks</VTab>
|
class="tab text-gray-400 uppercase"
|
||||||
<VTab value="transactions">Transactions</VTab>
|
:class="{ 'tab-active': tab === 'blocks' }"
|
||||||
</VTabs>
|
@click="tab = 'blocks'"
|
||||||
</VCardTitle>
|
>Blocks</a
|
||||||
<VWindow v-model="tab">
|
>
|
||||||
<VWindowItem value="blocks">
|
<a
|
||||||
<VTable>
|
class="tab text-gray-400 uppercase"
|
||||||
<thead>
|
:class="{ 'tab-active': tab === 'transactions' }"
|
||||||
<tr>
|
@click="tab = 'transactions'"
|
||||||
<th>Height</th>
|
>Transactions</a
|
||||||
<th>Hash</th>
|
>
|
||||||
<th>Proposer</th>
|
</div>
|
||||||
<th>Txs</th>
|
|
||||||
<th>Time</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="item in store.recents">
|
|
||||||
<td class="text-sm text-primary">
|
|
||||||
<RouterLink
|
|
||||||
:to="`/${props.chain}/block/${item.block?.header?.height}`"
|
|
||||||
>{{ item.block?.header?.height }}</RouterLink
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>{{ item.block_id?.hash }}</td>
|
|
||||||
<td>
|
|
||||||
{{ format.validator(item.block?.header?.proposer_address) }}
|
|
||||||
</td>
|
|
||||||
<td>{{ item.block?.data?.txs.length }}</td>
|
|
||||||
<td>{{ format.toDay(item.block?.header?.time, 'from') }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</VTable>
|
|
||||||
</VWindowItem>
|
|
||||||
<VWindowItem value="transactions">
|
|
||||||
<VTable>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Hash</th>
|
|
||||||
<th>Messages</th>
|
|
||||||
<th>Fees</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="item in store.txsInRecents">
|
|
||||||
<td>
|
|
||||||
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
|
|
||||||
item.hash
|
|
||||||
}}</RouterLink>
|
|
||||||
</td>
|
|
||||||
<td>{{ format.messages(item.tx.body.messages) }}</td>
|
|
||||||
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</VTable>
|
|
||||||
|
|
||||||
<VCardItem>
|
<div v-if="tab === 'blocks'" class="bg-base-100 rounded">
|
||||||
<v-alert
|
<VTable>
|
||||||
type="info"
|
<thead>
|
||||||
text="Only show txs in recent blocks"
|
<tr>
|
||||||
variant="tonal"
|
<th>Height</th>
|
||||||
></v-alert>
|
<th>Hash</th>
|
||||||
</VCardItem>
|
<th>Proposer</th>
|
||||||
</VWindowItem>
|
<th>Txs</th>
|
||||||
</VWindow>
|
<th>Time</th>
|
||||||
<VCardActions> </VCardActions>
|
</tr>
|
||||||
</VCard>
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in store.recents">
|
||||||
|
<td class="text-sm text-primary">
|
||||||
|
<RouterLink
|
||||||
|
:to="`/${props.chain}/block/${item.block?.header?.height}`"
|
||||||
|
>{{ item.block?.header?.height }}</RouterLink
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.block_id?.hash }}</td>
|
||||||
|
<td>
|
||||||
|
{{ format.validator(item.block?.header?.proposer_address) }}
|
||||||
|
</td>
|
||||||
|
<td>{{ item.block?.data?.txs.length }}</td>
|
||||||
|
<td>{{ format.toDay(item.block?.header?.time, 'from') }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</VTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-100 rounded" v-if="tab === 'transactions'">
|
||||||
|
<VTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hash</th>
|
||||||
|
<th>Messages</th>
|
||||||
|
<th>Fees</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in store.txsInRecents">
|
||||||
|
<td>
|
||||||
|
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
|
||||||
|
item.hash
|
||||||
|
}}</RouterLink>
|
||||||
|
</td>
|
||||||
|
<td>{{ format.messages(item.tx.body.messages) }}</td>
|
||||||
|
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</VTable>
|
||||||
|
<div class="p-4">
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
text="Only show txs in recent blocks"
|
||||||
|
variant="tonal"
|
||||||
|
></v-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<route>
|
<route>
|
||||||
|
|||||||
@ -100,8 +100,11 @@ const total = computed(()=> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const turnout = computed(() => {
|
const turnout = computed(() => {
|
||||||
|
if (total.value > 0) {
|
||||||
const bonded = useStakingStore().pool?.bonded_tokens || "1"
|
const bonded = useStakingStore().pool?.bonded_tokens || "1"
|
||||||
return format.percent(total.value / Number(bonded))
|
return format.percent(total.value / Number(bonded))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const yes = computed(()=> {
|
const yes = computed(()=> {
|
||||||
@ -135,75 +138,58 @@ const abstain = computed(()=> {
|
|||||||
}
|
}
|
||||||
return 0
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard>
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||||
<VCardItem>
|
<h2 class="card-title flex flex-col md:justify-between md:flex-row">
|
||||||
<VCardTitle>
|
<p class="truncate w-full">{{ proposal_id }}. {{ proposal.content?.title }} </p>
|
||||||
{{ proposal_id }}. {{ proposal.content?.title }} <VChip label :color="color" class="float-right">{{ status }}</VChip>
|
<div
|
||||||
</VCardTitle>
|
class="badge badge-ghost"
|
||||||
|
:class="
|
||||||
|
color === 'success'
|
||||||
|
? 'text-yes'
|
||||||
|
: color === 'error'
|
||||||
|
? 'text-no'
|
||||||
|
: 'text-info'
|
||||||
|
"
|
||||||
|
>{{ status }}</div>
|
||||||
|
</h2>
|
||||||
|
<div class="">
|
||||||
<ObjectElement :value="proposal.content"/>
|
<ObjectElement :value="proposal.content"/>
|
||||||
</VCardItem>
|
</div>
|
||||||
</VCard>
|
</div>
|
||||||
|
<!-- grid lg:grid-cols-3 auto-rows-max-->
|
||||||
<VRow class="my-5">
|
<!-- flex-col lg:flex-row flex -->
|
||||||
<VCol cols=12 md="4">
|
<div class="gap-4 mb-4 grid lg:grid-cols-3 auto-rows-max ">
|
||||||
<VCard class="h-100">
|
<!-- flex-1 -->
|
||||||
<VCardItem>
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow ">
|
||||||
<VCardTitle>Tally</VCardTitle>
|
<h2 class="card-title">Tally</h2>
|
||||||
<label>Turnout</label>
|
<div v-for="(item,index) of processList" :key="index">
|
||||||
<v-progress-linear
|
<label class="block">{{item.name }}</label>
|
||||||
:model-value="turnout"
|
<div class="h-6 w-full relative">
|
||||||
height="25"
|
<div class="absolute inset-x-0 inset-y-0 w-full opacity-10" :class="`${item.class}`"></div>
|
||||||
color="info"
|
<div class="absolute inset-x-0 inset-y-0" :class="`${item.class}`" :style="`width: ${item.value}`"></div>
|
||||||
>
|
<strong class="absolute inset-x-0 inset-y-0 text-center">{{ item.value }}</strong>
|
||||||
<strong>{{ turnout }}</strong>
|
</div>
|
||||||
</v-progress-linear>
|
</div>
|
||||||
<label>Yes</label>
|
</div>
|
||||||
<v-progress-linear
|
<!-- lg:col-span-2 -->
|
||||||
:model-value="yes"
|
<!-- lg:flex-[2_2_0%] -->
|
||||||
height="25"
|
<div class="h-max bg-base-100 px-4 pt-3 pb-4 rounded shadow lg:col-span-2">
|
||||||
color="success"
|
<h2 class="card-title">Timeline</h2>
|
||||||
>
|
<VTimeline
|
||||||
<strong>{{ yes }}</strong>
|
|
||||||
</v-progress-linear>
|
|
||||||
<label>No</label>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="no"
|
|
||||||
height="25"
|
|
||||||
color="error"
|
|
||||||
>
|
|
||||||
<strong>{{ no }}</strong>
|
|
||||||
</v-progress-linear>
|
|
||||||
<label>No With Veto</label>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="veto"
|
|
||||||
height="25"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<strong>{{ veto }}</strong>
|
|
||||||
</v-progress-linear>
|
|
||||||
<label>Abstain</label>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="abstain"
|
|
||||||
height="25"
|
|
||||||
color="dark"
|
|
||||||
>
|
|
||||||
<strong>{{ abstain }}</strong>
|
|
||||||
</v-progress-linear>
|
|
||||||
</VCardItem>
|
|
||||||
</VCard>
|
|
||||||
</VCol>
|
|
||||||
<VCol cols=12 md="8">
|
|
||||||
<VCard>
|
|
||||||
<VCardItem>
|
|
||||||
<VCardTitle>
|
|
||||||
Timeline
|
|
||||||
</VCardTitle>
|
|
||||||
<VTimeline
|
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
side="end"
|
side="end"
|
||||||
align="start"
|
align="start"
|
||||||
@ -301,12 +287,23 @@ const abstain = computed(()=> {
|
|||||||
</p>
|
</p>
|
||||||
</VTimelineItem>
|
</VTimelineItem>
|
||||||
</VTimeline>
|
</VTimeline>
|
||||||
</VCardItem>
|
|
||||||
</VCard>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
|
|
||||||
<VCard>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||||
|
<h2 class="card-title">Votes</h2>
|
||||||
|
<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>
|
||||||
|
<VBtn v-if="votePage.next_key" block variant="outlined" @click="loadMore()" :disabled="loading">Load more</VBtn>
|
||||||
|
</div>
|
||||||
|
<!-- <VCard>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>
|
<VCardTitle>
|
||||||
Votes
|
Votes
|
||||||
@ -321,6 +318,6 @@ const abstain = computed(()=> {
|
|||||||
</VTable>
|
</VTable>
|
||||||
<VBtn v-if="votePage.next_key" block variant="outlined" @click="loadMore()" :disabled="loading">Load more</VBtn>
|
<VBtn v-if="votePage.next_key" block variant="outlined" @click="loadMore()" :disabled="loading">Load more</VBtn>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
</VCard>
|
</VCard> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import { useGovStore } from '@/stores';
|
import { useGovStore } from '@/stores';
|
||||||
import ProposalListItem from '@/components/ProposalListItem.vue';
|
import ProposalListItem from '@/components/ProposalListItem.vue';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
const tab = ref('');
|
const tab = ref('2');
|
||||||
const store = useGovStore();
|
const store = useGovStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -13,33 +13,41 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const changeTab = (val: '2' | '3' | '4') => {
|
||||||
|
tab.value = val;
|
||||||
|
store.fetchProposals(val);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VTabs v-model="tab" class="v-tabs-pill">
|
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||||
<VTab value="2">Voting</VTab>
|
<a
|
||||||
<VTab value="3" @click="store.fetchProposals('3')">Passed</VTab>
|
class="tab text-gray-400 uppercase"
|
||||||
<VTab value="4" @click="store.fetchProposals('4')">Rejected</VTab>
|
:class="{ 'tab-active': tab === '2' }"
|
||||||
</VTabs>
|
@click="changeTab('2')"
|
||||||
<VWindow v-model="tab" class="mt-5">
|
>Voting</a
|
||||||
<VWindowItem value="2">
|
>
|
||||||
<ProposalListItem :proposals="store?.proposals['2']" />
|
<a
|
||||||
</VWindowItem>
|
class="tab text-gray-400 uppercase"
|
||||||
|
:class="{ 'tab-active': tab === '3' }"
|
||||||
<VWindowItem value="3">
|
@click="changeTab('3')"
|
||||||
<ProposalListItem :proposals="store?.proposals['3']" />
|
>Passed</a
|
||||||
</VWindowItem>
|
>
|
||||||
|
<a
|
||||||
<VWindowItem value="4">
|
class="tab text-gray-400 uppercase"
|
||||||
<ProposalListItem :proposals="store?.proposals['4']" />
|
:class="{ 'tab-active': tab === '4' }"
|
||||||
</VWindowItem>
|
@click="changeTab('4')"
|
||||||
</VWindow>
|
>Rejected</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<ProposalListItem :proposals="store?.proposals[tab]" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<route>
|
<route>
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
i18n: 'governance'
|
i18n: 'governance'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</route>
|
}
|
||||||
|
</route>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<!-- Chain ID -->
|
<!-- Chain ID -->
|
||||||
<div class="bg-card px-4 pt-3 pb-4 rounded">
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded">
|
||||||
<div class="text-base mb-3 text-main">{{ chain.title }}</div>
|
<div class="text-base mb-3 text-main">{{ chain.title }}</div>
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 2xl:grid-cols-6 gap-4"
|
class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 2xl:grid-cols-6 gap-4"
|
||||||
@ -40,13 +40,13 @@ onMounted(() => {
|
|||||||
<!-- Slashing Parameters -->
|
<!-- Slashing Parameters -->
|
||||||
<CardParameter :cardItem="store.slashing"/>
|
<CardParameter :cardItem="store.slashing"/>
|
||||||
<!-- Application Version -->
|
<!-- Application Version -->
|
||||||
<div class="bg-card px-4 pt-3 pb-4 rounded-sm mt-6">
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded-sm mt-6">
|
||||||
<div class="text-base mb-3 text-main">{{ store.appVersion?.title }}</div>
|
<div class="text-base mb-3 text-main">{{ store.appVersion?.title }}</div>
|
||||||
<ArrayObjectElement :value="store.appVersion?.items" :thead="false"/>
|
<ArrayObjectElement :value="store.appVersion?.items" :thead="false"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Node Information -->
|
<!-- Node Information -->
|
||||||
<div class="bg-card px-4 pt-3 pb-4ß rounded-sm mt-6">
|
<div class="bg-base-100 px-4 pt-3 pb-4ß rounded-sm mt-6">
|
||||||
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
|
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
|
||||||
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false"/>
|
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
<script lang=ts setup>
|
<script lang="ts" setup>
|
||||||
import { useBaseStore, useFormatter, useStakingStore } from '@/stores';
|
import { useBaseStore, useFormatter, useStakingStore } from '@/stores';
|
||||||
import { toBase64, toHex } from '@cosmjs/encoding';
|
import { toBase64, toHex } from '@cosmjs/encoding';
|
||||||
import { computed } from '@vue/reactivity';
|
import { computed } from '@vue/reactivity';
|
||||||
import { onMounted, ref, type DebuggerEvent } from 'vue';
|
import { onMounted, ref, type DebuggerEvent } from 'vue';
|
||||||
import { consensusPubkeyToHexAddress } from '@/libs'
|
import { consensusPubkeyToHexAddress } from '@/libs';
|
||||||
import type { Key, Validator } from '@/types';
|
import type { Key, Validator } from '@/types';
|
||||||
const staking = useStakingStore()
|
const staking = useStakingStore();
|
||||||
const format = useFormatter()
|
const format = useFormatter();
|
||||||
|
|
||||||
const cache = JSON.parse(localStorage.getItem('avatars')||'{}')
|
const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
|
||||||
const avatars = ref( cache || {} )
|
const avatars = ref(cache || {});
|
||||||
const latest = ref({} as Record<string, number>)
|
const latest = ref({} as Record<string, number>);
|
||||||
const yesterday = ref({} as Record<string, number>)
|
const yesterday = ref({} as Record<string, number>);
|
||||||
const tab = ref('active')
|
const tab = ref('active');
|
||||||
const unbondList = ref([] as Validator[])
|
const unbondList = ref([] as Validator[]);
|
||||||
const base = useBaseStore()
|
const base = useBaseStore();
|
||||||
onMounted(()=> {
|
onMounted(() => {
|
||||||
fetchChange(0)
|
fetchChange(0);
|
||||||
staking.fetchInacitveValdiators().then(x => {
|
staking.fetchInacitveValdiators().then((x) => {
|
||||||
unbondList.value = x
|
unbondList.value = x;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
function fetchChange(offset: number) {
|
function fetchChange(offset: number) {
|
||||||
const base = useBaseStore()
|
const base = useBaseStore();
|
||||||
const diff = 86400000 / base.blocktime
|
const diff = 86400000 / base.blocktime;
|
||||||
// base.fetchAbciInfo().then(h => {
|
// base.fetchAbciInfo().then(h => {
|
||||||
// // console.log('block:', h)
|
// // console.log('block:', h)
|
||||||
// base.fetchValidatorByHeight(h.lastBlockHeight, offset).then(x => {
|
// base.fetchValidatorByHeight(h.lastBlockHeight, offset).then(x => {
|
||||||
@ -44,190 +44,233 @@ function fetchChange(offset: number) {
|
|||||||
|
|
||||||
const change24 = (key: Key) => {
|
const change24 = (key: Key) => {
|
||||||
// console.log('hex key:', consensusPubkeyToHexAddress(key))
|
// console.log('hex key:', consensusPubkeyToHexAddress(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;
|
||||||
}
|
};
|
||||||
|
|
||||||
const change24Text = (key?: Key) => {
|
const change24Text = (key?: Key) => {
|
||||||
if(!key) return ''
|
if (!key) return '';
|
||||||
const v = change24(key)
|
const v = change24(key);
|
||||||
return v!==0 ? format.numberAndSign(v) : ''
|
return v !== 0 ? format.numberAndSign(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 update = (m: DebuggerEvent) => {
|
const update = (m: DebuggerEvent) => {
|
||||||
if(m.key === 'validators') {
|
if (m.key === 'validators') {
|
||||||
loadAvatars()
|
loadAvatars();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
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(() => new Promise(resolve => {
|
promise = promise.then(
|
||||||
const identity = item.description?.identity
|
() =>
|
||||||
if(identity && !avatars.value[identity]){
|
new Promise((resolve) => {
|
||||||
staking.keybase(identity).then(d => {
|
const identity = item.description?.identity;
|
||||||
if (Array.isArray(d.them) && d.them.length > 0) {
|
if (identity && !avatars.value[identity]) {
|
||||||
const uri = String(d.them[0]?.pictures?.primary?.url).replace("https://s3.amazonaws.com/keybase_processed_uploads/", "")
|
staking.keybase(identity).then((d) => {
|
||||||
if(uri) {
|
if (Array.isArray(d.them) && d.them.length > 0) {
|
||||||
avatars.value[identity] = uri
|
const uri = String(d.them[0]?.pictures?.primary?.url).replace(
|
||||||
localStorage.setItem('avatars', JSON.stringify(avatars.value))
|
'https://s3.amazonaws.com/keybase_processed_uploads/',
|
||||||
}
|
''
|
||||||
|
);
|
||||||
|
if (uri) {
|
||||||
|
avatars.value[identity] = uri;
|
||||||
|
localStorage.setItem(
|
||||||
|
'avatars',
|
||||||
|
JSON.stringify(avatars.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
resolve()
|
}
|
||||||
})
|
resolve();
|
||||||
}else{
|
});
|
||||||
resolve()
|
} else {
|
||||||
}
|
resolve();
|
||||||
}))
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
staking.$subscribe((m, s)=> {
|
|
||||||
if (Array.isArray(m.events)) {
|
|
||||||
m.events.forEach(x => {
|
|
||||||
update(x)
|
|
||||||
})
|
})
|
||||||
} else {
|
);
|
||||||
update(m.events)
|
});
|
||||||
}
|
};
|
||||||
})
|
|
||||||
const logo = (identity?: string) => {
|
|
||||||
if(!identity) return ''
|
|
||||||
const url = avatars.value[identity] || ''
|
|
||||||
return url.startsWith('http')? url: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`
|
|
||||||
}
|
|
||||||
const rank = function(position: number) {
|
|
||||||
let sum = 0
|
|
||||||
for(let i = 0;i < position; i++) {
|
|
||||||
sum += Number(staking.validators[i]?.delegator_shares)
|
|
||||||
}
|
|
||||||
const percent = (sum / staking.totalPower)
|
|
||||||
|
|
||||||
switch (true) {
|
staking.$subscribe((m, s) => {
|
||||||
case tab.value ==='active' && percent < 0.33: return 'error'
|
if (Array.isArray(m.events)) {
|
||||||
case tab.value ==='active' && percent < 0.67: return 'warning'
|
m.events.forEach((x) => {
|
||||||
default: return 'primary'
|
update(x);
|
||||||
}
|
});
|
||||||
}
|
} else {
|
||||||
|
update(m.events);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const logo = (identity?: string) => {
|
||||||
|
if (!identity) return '';
|
||||||
|
const url = avatars.value[identity] || '';
|
||||||
|
return url.startsWith('http')
|
||||||
|
? url
|
||||||
|
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
|
||||||
|
};
|
||||||
|
const rank = function (position: number) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < position; i++) {
|
||||||
|
sum += Number(staking.validators[i]?.delegator_shares);
|
||||||
|
}
|
||||||
|
const percent = sum / staking.totalPower;
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case tab.value === 'active' && percent < 0.33:
|
||||||
|
return 'error';
|
||||||
|
case tab.value === 'active' && percent < 0.67:
|
||||||
|
return 'warning';
|
||||||
|
default:
|
||||||
|
return 'primary';
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||||
|
<a
|
||||||
|
class="tab text-gray-400"
|
||||||
|
:class="{ 'tab-active': tab === 'active' }"
|
||||||
|
@click="tab = 'active'"
|
||||||
|
>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>
|
<div>
|
||||||
<VCard>
|
<VCard>
|
||||||
<VCardTitle class="d-flex justify-space-between">
|
|
||||||
<VBtnToggle v-model="tab" size="small" color="primary">
|
|
||||||
<VBtn value="active" variant="outlined" >Active</VBtn>
|
|
||||||
<VBtn value="inactive" variant="outlined">Inactive</VBtn>
|
|
||||||
</VBtnToggle>
|
|
||||||
<span class="mt-2">{{ list.length }}/{{ staking.params.max_validators }}</span>
|
|
||||||
</VCardTitle>
|
|
||||||
<VTable class="text-no-wrap table-header-bg rounded-0">
|
<VTable class="text-no-wrap table-header-bg rounded-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th scope="col" style="width: 3rem">#</th>
|
||||||
scope="col"
|
<th scope="col">VALIDATOR</th>
|
||||||
style="width: 3rem;"
|
<th scope="col" class="text-right">VOTING POWER</th>
|
||||||
>#</th>
|
<th scope="col" class="text-right">24h CHANGES</th>
|
||||||
<th scope="col">
|
<th scope="col" class="text-right">COMMISSION</th>
|
||||||
VALIDATOR
|
<th scope="col">ACTIONS</th>
|
||||||
</th>
|
</tr>
|
||||||
<th scope="col" class="text-right">
|
</thead>
|
||||||
VOTING POWER
|
<tbody>
|
||||||
</th>
|
<tr v-for="(v, i) in list" :key="v.operator_address">
|
||||||
<th scope="col" class="text-right">
|
<!-- 👉 rank -->
|
||||||
24h CHANGES
|
<td>
|
||||||
</th>
|
<VChip label :color="rank(i)">
|
||||||
<th scope="col" class="text-right">
|
{{ i + 1 }}
|
||||||
COMMISSION
|
</VChip>
|
||||||
</th>
|
</td>
|
||||||
<th scope="col">
|
|
||||||
ACTIONS
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(v, i) in list"
|
|
||||||
:key="v.operator_address"
|
|
||||||
>
|
|
||||||
<!-- 👉 rank -->
|
|
||||||
<td>
|
|
||||||
<VChip label :color="rank(i)">
|
|
||||||
{{ i + 1 }}
|
|
||||||
</VChip>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- 👉 Validator -->
|
<!-- 👉 Validator -->
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-center overflow-hidden" style="max-width: 400px;">
|
<div
|
||||||
<VAvatar
|
class="d-flex align-center overflow-hidden"
|
||||||
variant="tonal"
|
style="max-width: 400px"
|
||||||
class="me-3"
|
>
|
||||||
size="34"
|
<VAvatar
|
||||||
icon="mdi-help-circle-outline"
|
variant="tonal"
|
||||||
:image="logo(v.description?.identity)"
|
class="me-3"
|
||||||
/>
|
size="34"
|
||||||
<div class="d-flex flex-column">
|
icon="mdi-help-circle-outline"
|
||||||
<h6 class="text-sm text-primary">
|
:image="logo(v.description?.identity)"
|
||||||
<RouterLink
|
/>
|
||||||
:to="{name: 'chain-staking-validator', params: {validator: v.operator_address}}"
|
<div class="d-flex flex-column">
|
||||||
class="font-weight-medium user-list-name"
|
<h6 class="text-sm text-primary">
|
||||||
>
|
<RouterLink
|
||||||
{{ v.description?.moniker }}
|
:to="{
|
||||||
</RouterLink>
|
name: 'chain-staking-validator',
|
||||||
|
params: { validator: v.operator_address },
|
||||||
</h6>
|
}"
|
||||||
<span class="text-xs">{{ v.description?.website || v.description?.identity || '-' }}</span>
|
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>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- 👉 Voting Power -->
|
<!-- 👉 Voting Power -->
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<h6 class="text-sm font-weight-medium">
|
<h6 class="text-sm font-weight-medium">
|
||||||
{{ format.formatToken( {amount: parseInt(v.tokens).toString(), denom: staking.params.bond_denom }, true, "0,0") }}
|
{{
|
||||||
|
format.formatToken(
|
||||||
|
{
|
||||||
|
amount: parseInt(v.tokens).toString(),
|
||||||
|
denom: staking.params.bond_denom,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
'0,0'
|
||||||
|
)
|
||||||
|
}}
|
||||||
</h6>
|
</h6>
|
||||||
<span class="text-xs">{{ format.calculatePercent(v.delegator_shares, staking.totalPower) }}</span>
|
<span class="text-xs">{{
|
||||||
|
format.calculatePercent(
|
||||||
|
v.delegator_shares,
|
||||||
|
staking.totalPower
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- 👉 24h Changes -->
|
<!-- 👉 24h Changes -->
|
||||||
<td class="text-right text-xs" :class="change24Color(v.consensus_pubkey)">
|
<td
|
||||||
{{ change24Text(v.consensus_pubkey) }} <VChip label v-if="v.jailed" color="error">Jailed</VChip>
|
class="text-right text-xs"
|
||||||
</td>
|
:class="change24Color(v.consensus_pubkey)"
|
||||||
<!-- 👉 commission -->
|
>
|
||||||
<td class="text-right">
|
{{ change24Text(v.consensus_pubkey) }}
|
||||||
{{ format.formatCommissionRate(v.commission?.commission_rates?.rate) }}
|
<VChip label v-if="v.jailed" color="error">Jailed</VChip>
|
||||||
</td>
|
</td>
|
||||||
<!-- 👉 Action -->
|
<!-- 👉 commission -->
|
||||||
<td>
|
<td class="text-right">
|
||||||
{{ 2 }}
|
{{
|
||||||
</td>
|
format.formatCommissionRate(
|
||||||
|
v.commission?.commission_rates?.rate
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<!-- 👉 Action -->
|
||||||
|
<td>
|
||||||
|
{{ 2 }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</VTable>
|
</VTable>
|
||||||
<VDivider/>
|
<VDivider />
|
||||||
<VCardActions class="py-2">
|
<VCardActions class="py-2">
|
||||||
<VChip label color="error">Top 33%</VChip> <VChip label color="warning" class="mx-2">Top 67%</VChip>
|
<VChip label color="error">Top 33%</VChip>
|
||||||
|
<VChip label color="warning" class="mx-2">Top 67%</VChip>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<route>
|
<route>
|
||||||
|
|||||||
@ -1,24 +1,29 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useDashboard, LoadingStatus, type ChainConfig } from '@/stores/useDashboard';
|
import {
|
||||||
|
useDashboard,
|
||||||
|
LoadingStatus,
|
||||||
|
type ChainConfig,
|
||||||
|
} from '@/stores/useDashboard';
|
||||||
import ChainSummary from '@/components/ChainSummary.vue';
|
import ChainSummary from '@/components/ChainSummary.vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useBlockchain } from '@/stores';
|
import { useBlockchain } from '@/stores';
|
||||||
|
|
||||||
const dashboard = useDashboard()
|
const dashboard = useDashboard();
|
||||||
|
|
||||||
dashboard.$subscribe((mutation, state) => {
|
dashboard.$subscribe((mutation, state) => {
|
||||||
localStorage.setItem('favorite', JSON.stringify(state.favorite))
|
localStorage.setItem('favorite', JSON.stringify(state.favorite));
|
||||||
})
|
});
|
||||||
const keywords = ref('')
|
const keywords = ref('');
|
||||||
const chains = computed(() => {
|
const chains = computed(() => {
|
||||||
if (keywords.value) {
|
if (keywords.value) {
|
||||||
return Object.values(dashboard.chains).filter((x: ChainConfig) => x.chainName.indexOf(keywords.value) > -1)
|
return Object.values(dashboard.chains).filter(
|
||||||
|
(x: ChainConfig) => x.chainName.indexOf(keywords.value) > -1
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Object.values(dashboard.chains)
|
return Object.values(dashboard.chains);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
const chain = useBlockchain()
|
const chain = useBlockchain();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="">
|
||||||
@ -29,29 +34,43 @@ const chain = useBlockchain()
|
|||||||
<h1 class="text-primary text-3xl md:text-6xl font-bold mr-2">
|
<h1 class="text-primary text-3xl md:text-6xl font-bold mr-2">
|
||||||
Ping dashboard
|
Ping dashboard
|
||||||
</h1>
|
</h1>
|
||||||
<div class="badge badge-info badge-outline mt-1 text-sm md:mt-8">Beta</div>
|
<div class="badge badge-info badge-outline mt-1 text-sm md:mt-8">
|
||||||
|
Beta
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center text-base">
|
<div class="text-center text-base">
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
{{ $t('index.slogan') }}
|
{{ $t('index.slogan') }}
|
||||||
</p>
|
</p>
|
||||||
<h2 class="mb-6">
|
<h2 class="mb-6">Cosmos Ecosystem Blockchains 🚀</h2>
|
||||||
Cosmos Ecosystem Blockchains 🚀
|
</div>
|
||||||
</h2>
|
<div
|
||||||
|
v-if="dashboard.status !== LoadingStatus.Loaded"
|
||||||
|
class="flex justify-center"
|
||||||
|
>
|
||||||
|
<progress class="progress progress-info w-80 h-1"></progress>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="dashboard.status !== LoadingStatus.Loaded" class="flex justify-center"><progress
|
|
||||||
class="progress progress-info w-80 h-1"></progress></div>
|
|
||||||
|
|
||||||
<VTextField v-model="keywords" variant="underlined" :placeholder="$t('index.search_placeholder')"
|
<VTextField
|
||||||
style="max-width: 300px;" app>
|
v-model="keywords"
|
||||||
|
variant="underlined"
|
||||||
|
:placeholder="$t('index.search_placeholder')"
|
||||||
|
style="max-width: 300px"
|
||||||
|
app
|
||||||
|
>
|
||||||
<template #append-inner>
|
<template #append-inner>
|
||||||
{{ chains.length }}/{{ dashboard.length }}
|
{{ chains.length }}/{{ dashboard.length }}
|
||||||
</template>
|
</template>
|
||||||
</VTextField>
|
</VTextField>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 mt-6 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5">
|
<div
|
||||||
<ChainSummary v-for="(chain, index) in chains" :key="index" :name="chain.chainName" />
|
class="grid grid-cols-2 gap-4 mt-6 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5"
|
||||||
|
>
|
||||||
|
<ChainSummary
|
||||||
|
v-for="(chain, index) in chains"
|
||||||
|
:key="index"
|
||||||
|
:name="chain.chainName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
|
import { defineEmits, ref, watch } from 'vue'
|
||||||
import type { Anchor } from 'vuetify/lib/components';
|
import type { Anchor } from 'vuetify/lib/components';
|
||||||
import type { I18nLanguage } from '@layouts/types';
|
import type { I18nLanguage } from '@layouts/types';
|
||||||
|
|
||||||
@ -7,7 +8,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
location: 'bottom end',
|
location: 'bottom end',
|
||||||
});
|
});
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change', id: string): void;
|
(e: 'change', id: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -16,30 +17,37 @@ interface Props {
|
|||||||
location?: Anchor;
|
location?: Anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { locale } = useI18n({ useScope: 'global' });
|
let locale = ref(useI18n({ useScope: 'global' }).locale)
|
||||||
|
watch(locale, (val: string) => {
|
||||||
watch(locale, (val) => {
|
|
||||||
document.documentElement.setAttribute('lang', val as string);
|
document.documentElement.setAttribute('lang', val as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentLang = ref([localStorage.getItem('lang') || 'en']);
|
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
||||||
|
|
||||||
|
function changeLang(lang: string){
|
||||||
|
locale.value = lang
|
||||||
|
currentLang.value = lang
|
||||||
|
emit('change', lang)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown dropdown-end">
|
<div
|
||||||
|
class="dropdown"
|
||||||
|
:class="currentLang === 'ar'?'dropdown-right': 'dropdown-bottom dropdown-end'"
|
||||||
|
>
|
||||||
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
||||||
<Icon icon="mdi-translate" style="font-size: 24px" />
|
<Icon icon="mdi-translate" class="text-2xl" />
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52"
|
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
||||||
>
|
>
|
||||||
<li v-for="lang in props.languages" :key="lang.i18nLang">
|
<li v-for="lang in props.languages" :key="lang.i18nLang">
|
||||||
<a
|
<a
|
||||||
@click="
|
class="hover:bg-base-content"
|
||||||
locale = lang.i18nLang;
|
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
||||||
$emit('change', lang.i18nLang);
|
@click="changeLang(lang.i18nLang)"
|
||||||
"
|
|
||||||
>{{ lang.label }}</a
|
>{{ lang.label }}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -6,16 +6,12 @@
|
|||||||
:root {
|
:root {
|
||||||
--text-main: #333;
|
--text-main: #333;
|
||||||
--text-secondary: #4b525d;
|
--text-secondary: #4b525d;
|
||||||
--bg-card: #fff;
|
|
||||||
--bg-active: #fbfbfc;
|
--bg-active: #fbfbfc;
|
||||||
--bg-hover: #eee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
--text-main: #f7f7f7;
|
--text-main: #f7f7f7;
|
||||||
--text-secondary: #6f6e84;
|
--text-secondary: #6f6e84;
|
||||||
--bg-card: #28334e;
|
|
||||||
--bg-active: #242b40;
|
--bg-active: #242b40;
|
||||||
--bg-hover: #303044;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,34 +11,30 @@ module.exports = {
|
|||||||
primary: '#666cff',
|
primary: '#666cff',
|
||||||
main: 'var(--text-main)',
|
main: 'var(--text-main)',
|
||||||
secondary: 'var(--text-secondary)',
|
secondary: 'var(--text-secondary)',
|
||||||
card: 'var(--bg-card)',
|
|
||||||
hover: 'var(--bg-hover)',
|
|
||||||
active: 'var(--bg-active)',
|
active: 'var(--bg-active)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [require('daisyui')],
|
||||||
require("daisyui")
|
|
||||||
],
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
themes: [
|
themes: [
|
||||||
{
|
|
||||||
myTheme: {
|
|
||||||
info: "#666CFF",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
light: {
|
light: {
|
||||||
...require("daisyui/src/colors/themes")["[data-theme=light]"],
|
...require('daisyui/src/colors/themes')['[data-theme=light]'],
|
||||||
info: "#666CFF",
|
primary: '#666cff',
|
||||||
}
|
info: '#666CFF',
|
||||||
|
'base-content': '#e9eaeb'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dark: {
|
dark: {
|
||||||
...require("daisyui/src/colors/themes")["[data-theme=dark]"],
|
...require('daisyui/src/colors/themes')['[data-theme=dark]'],
|
||||||
info: "#666CFF",
|
primary: '#666cff',
|
||||||
}
|
info: '#666CFF',
|
||||||
|
'base-100': '#2a334c',
|
||||||
|
'base-content': '#373f57'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user