forked from cerc-io/cosmos-explorer
commit
fdf5e07db6
@ -1,8 +1,8 @@
|
||||
{
|
||||
"chain_name": "desmos",
|
||||
"coingecko": "desmos",
|
||||
"api": ["https://api.mainnet.desmos.network"],
|
||||
"rpc": ["https://rpc.mainnet.desmos.network:443"],
|
||||
"api": ["https://api.mainnet.desmos.network", "https://desmos-api.lavenderfive.com"],
|
||||
"rpc": ["https://rpc.mainnet.desmos.network:443", "https://desmos-rpc.lavenderfive.com:443"],
|
||||
"sdk_version": "0.45.8",
|
||||
"coin_type": "118",
|
||||
"min_tx_fee": "3000",
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"chain_name": "lum-network",
|
||||
"coingecko": "lum-network",
|
||||
"api": ["https://api-lum.degeno.de", "https://node0.mainnet.lum.network/rest"],
|
||||
"rpc": ["https://rpc-lum.degeno.de:443", "https://node0.mainnet.lum.network:443/rpc"],
|
||||
"api": ["https://node0.mainnet.lum.network/rest", "https://lumnetwork-api.lavenderfive.com"],
|
||||
"rpc": ["https://node0.mainnet.lum.network:443/rpc", "https://lumnetwork-rpc.lavenderfive.com:443"],
|
||||
"snapshot_provider": "",
|
||||
"sdk_version": "0.44.5",
|
||||
"coin_type": "880",
|
||||
|
@ -2,8 +2,8 @@
|
||||
{
|
||||
"chain_name": "shentu",
|
||||
"coingecko": "certik",
|
||||
"api": ["https://certik-api.polkachu.com", "https://chainfull.noopsbycertik.com"],
|
||||
"rpc": ["https://certik-rpc.polkachu.com:443"],
|
||||
"api": ["https://shentu-api.polkachu.com", "https://chainfull.noopsbycertik.com"],
|
||||
"rpc": ["https://shentu-rpc.polkachu.com:443"],
|
||||
"snapshot_provider": "",
|
||||
"sdk_version": "0.45.9",
|
||||
"coin_type": "118",
|
||||
|
19
chains/mainnet/terp.json
Normal file
19
chains/mainnet/terp.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"chain_name": "terp",
|
||||
"coingecko": "",
|
||||
"api": ["https://lcd.terpnetwork.hexnodes.co"],
|
||||
"rpc": ["https://rpc.terpnetwork.hexnodes.co"],
|
||||
"snapshot_provider": "",
|
||||
"sdk_version": "0.47.1",
|
||||
"coin_type": "118",
|
||||
"min_tx_fee": "500",
|
||||
"addr_prefix": "terp",
|
||||
"logo": "/logos/terp-network.jpg",
|
||||
"assets": [{
|
||||
"base": "uterp",
|
||||
"symbol": "TERP",
|
||||
"exponent": "6",
|
||||
"coingecko_id": "",
|
||||
"logo": "/logos/terp-network.jpg"
|
||||
}]
|
||||
}
|
@ -35,7 +35,7 @@
|
||||
"md-editor-v3": "^2.8.1",
|
||||
"numeral": "^2.0.6",
|
||||
"osmojs": "^14.0.0-rc.0",
|
||||
"ping-widget": "^0.0.26",
|
||||
"ping-widget": "^0.0.30",
|
||||
"pinia": "^2.0.28",
|
||||
"postcss": "^8.4.23",
|
||||
"qrcode": "^1.5.3",
|
||||
|
BIN
public/logos/ledger.jpeg
Normal file
BIN
public/logos/ledger.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
public/logos/ledger.webp
Normal file
BIN
public/logos/ledger.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1002 B |
BIN
public/logos/terp-network.jpg
Normal file
BIN
public/logos/terp-network.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -40,7 +40,7 @@ function gotoPage(pageNum: number) {
|
||||
<div v-if="total && limit" class="btn-group">
|
||||
<button v-for="{ page, color } in pages" :key="page"
|
||||
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white" :class="{
|
||||
'!bg-primary text-white': color === 'btn-primary',
|
||||
'!btn-primary': color === 'btn-primary',
|
||||
}" @click="gotoPage(page)">
|
||||
{{ page }}
|
||||
</button>
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
useBlockchain,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useTxDialog
|
||||
useTxDialog,
|
||||
} from '@/stores';
|
||||
import { select } from '@/components/dynamic/index';
|
||||
import type { PaginatedProposals } from '@/types';
|
||||
@ -45,55 +45,94 @@ const proposalInfo = ref();
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in proposals?.proposals" :key="index">
|
||||
<td class="px-4 w-20">
|
||||
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item">
|
||||
#{{ item?.proposal_id }}</label>
|
||||
<label
|
||||
for="proposal-detail-modal"
|
||||
class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item"
|
||||
>
|
||||
#{{ item?.proposal_id }}</label
|
||||
>
|
||||
</td>
|
||||
<td class="w-full">
|
||||
<div>
|
||||
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate">
|
||||
<RouterLink
|
||||
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
|
||||
>
|
||||
{{ item?.content?.title }}
|
||||
</RouterLink>
|
||||
<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']) }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<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 class="w-36">
|
||||
<div class="pl-4">
|
||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
">
|
||||
<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="flex items-center"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] ===
|
||||
'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
"
|
||||
>
|
||||
<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">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
{{
|
||||
statusMap?.[item?.status] ||
|
||||
item?.status
|
||||
}}
|
||||
</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') }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td v-if="statusMap?.[item?.status] === 'VOTING'" class="w-40">
|
||||
<td
|
||||
v-if="statusMap?.[item?.status] === 'VOTING'"
|
||||
class="w-40"
|
||||
>
|
||||
<div class="">
|
||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">
|
||||
<span v-if="item?.voterStatus">{{ item?.voterStatus.replace("VOTE_OPTION_", "") }}</span>
|
||||
<label
|
||||
for="vote"
|
||||
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>
|
||||
</label>
|
||||
</div>
|
||||
@ -103,56 +142,99 @@ const proposalInfo = ref();
|
||||
</table>
|
||||
|
||||
<div class="lg:!hidden">
|
||||
<div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4">
|
||||
<div 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
|
||||
v-for="(item, index) in proposals?.proposals"
|
||||
:key="index"
|
||||
class="px-4 py-4"
|
||||
>
|
||||
<div
|
||||
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 class="grid grid-cols-4 mt-2 mb-2">
|
||||
<div class="col-span-2">
|
||||
<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']) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
">
|
||||
<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="flex items-center"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
"
|
||||
>
|
||||
<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">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
</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') }}
|
||||
</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 class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
|
||||
<div class="" v-show="item?.voterStatus === 'No With Veto'">
|
||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">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>
|
||||
<label
|
||||
for="vote"
|
||||
class="btn btn-xs btn-primary rounded-sm"
|
||||
@click="
|
||||
dialog.open('vote', {
|
||||
proposal_id: item?.proposal_id,
|
||||
})
|
||||
"
|
||||
>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 }}
|
||||
</div>
|
||||
</div>
|
||||
@ -160,15 +242,30 @@ const proposalInfo = ref();
|
||||
</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 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>
|
||||
<p class="py-4">
|
||||
<Component v-if="proposalInfo?.content?.description"
|
||||
:is="select(proposalInfo?.content?.description, 'horizontal')"
|
||||
:value="proposalInfo?.content?.description">
|
||||
<Component
|
||||
v-if="proposalInfo?.content?.description"
|
||||
:is="
|
||||
select(
|
||||
proposalInfo?.content?.description,
|
||||
'horizontal'
|
||||
)
|
||||
"
|
||||
:value="proposalInfo?.content?.description"
|
||||
>
|
||||
</Component>
|
||||
</p>
|
||||
</label>
|
||||
|
@ -1,50 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
themes: {
|
||||
name: string;
|
||||
icon: string;
|
||||
}[];
|
||||
}>();
|
||||
|
||||
const themeMap = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
|
||||
|
||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
||||
|
||||
const changeMode = () => {
|
||||
let value = 'dark';
|
||||
if (theme.value === 'dark') {
|
||||
value = 'light';
|
||||
}
|
||||
if (
|
||||
theme.value === 'system' &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
) {
|
||||
value = 'dark';
|
||||
}
|
||||
if (value === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.remove('light');
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', value);
|
||||
window.localStorage.setItem('theme', value);
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tooltip tooltip-bottom delay-1000">
|
||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode">
|
||||
<Icon :icon="props.themes[theme].icon" class="text-2xl" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
@ -1,21 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import ApexCharts from 'vue3-apexcharts';
|
||||
import { computed } from 'vue';
|
||||
import { useBaseStore } from '@/stores';
|
||||
import { getDonutChartConfig } from './apexChartConfig';
|
||||
|
||||
const props = defineProps(['series', 'labels']);
|
||||
|
||||
const expenseRationChartConfig = computed(() =>
|
||||
getDonutChartConfig(window.localStorage.getItem('theme') || 'dark', props.labels)
|
||||
);
|
||||
const baseStore = useBaseStore();
|
||||
|
||||
const expenseRationChartConfig = computed(() => {
|
||||
const theme = baseStore.theme;
|
||||
getDonutChartConfig(theme, props.labels);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" />
|
||||
<ApexCharts
|
||||
type="donut"
|
||||
height="410"
|
||||
:options="expenseRationChartConfig"
|
||||
:series="series"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DonetChart',
|
||||
name: 'DonetChart',
|
||||
};
|
||||
</script>
|
||||
|
@ -3,40 +3,56 @@ import ApexCharts from 'vue3-apexcharts';
|
||||
import { getMarketPriceChartConfig } from './apexChartConfig';
|
||||
import { useIndexModule } from '@/modules/[chain]/indexStore';
|
||||
import { computed, ref } from '@vue/reactivity';
|
||||
import { useBaseStore } from '@/stores';
|
||||
|
||||
const store = useIndexModule();
|
||||
const baseStore = useBaseStore();
|
||||
const chartConfig = computed(() => {
|
||||
const labels = store.marketData.prices.map((item: any) => item[0]);
|
||||
return getMarketPriceChartConfig(window.localStorage.getItem('theme') || 'dark', labels);
|
||||
const theme = baseStore.theme;
|
||||
const labels = store.marketData.prices.map((item: any) => item[0]);
|
||||
return getMarketPriceChartConfig(theme, labels);
|
||||
});
|
||||
const kind = ref('price');
|
||||
const series = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: 'Price',
|
||||
data:
|
||||
kind.value === 'price'
|
||||
? store.marketData.prices.map((item: any) => item[1])
|
||||
: store.marketData.total_volumes.map((item: any) => item[1]),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'Price',
|
||||
data:
|
||||
kind.value === 'price'
|
||||
? store.marketData.prices.map((item: any) => item[1])
|
||||
: store.marketData.total_volumes.map(
|
||||
(item: any) => item[1]
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function changeChart(type: string) {
|
||||
kind.value = type;
|
||||
kind.value = type;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tabs tabs-boxed bg-transparent justify-end">
|
||||
<a class="tab text-xs mr-2 text-gray-400 uppercase" :class="{ 'tab-active': kind === 'price' }"
|
||||
@click="changeChart('price')">
|
||||
Price
|
||||
</a>
|
||||
<a class="tab text-xs text-gray-400 uppercase" :class="{ 'tab-active': kind === 'volume' }"
|
||||
@click="changeChart('volume')">
|
||||
Volume
|
||||
</a>
|
||||
</div>
|
||||
<ApexCharts type="area" height="230" :options="chartConfig" :series="series" />
|
||||
<div class="tabs tabs-boxed bg-transparent justify-end">
|
||||
<a
|
||||
class="tab text-xs mr-2 text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': kind === 'price' }"
|
||||
@click="changeChart('price')"
|
||||
>
|
||||
Price
|
||||
</a>
|
||||
<a
|
||||
class="tab text-xs text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': kind === 'volume' }"
|
||||
@click="changeChart('volume')"
|
||||
>
|
||||
Volume
|
||||
</a>
|
||||
</div>
|
||||
<ApexCharts
|
||||
type="area"
|
||||
height="230"
|
||||
:options="chartConfig"
|
||||
:series="series"
|
||||
/>
|
||||
</template>
|
||||
|
@ -18,7 +18,7 @@ const header = computed(() => {
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="overflow-x-auto p-4">
|
||||
<div class="overflow-auto h-[500px] p-4">
|
||||
<div
|
||||
v-if="header.length > 0"
|
||||
class="table table-compact w-full"
|
||||
@ -35,7 +35,7 @@ const header = computed(() => {
|
||||
:key="index"
|
||||
class="text-left text-capitalize"
|
||||
>
|
||||
{{ item }}
|
||||
{{ item }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -4,11 +4,16 @@ import { select } from './index';
|
||||
const props = defineProps(['value']);
|
||||
</script>
|
||||
<template>
|
||||
<div class="overflow-x-auto">
|
||||
<div class="overflow-auto">
|
||||
<table class="table table-compact w-full text-sm">
|
||||
<tbody>
|
||||
<tr v-for="(v, k) of value">
|
||||
<td class="text-capitalize whitespace-break-spaces w-1/5" style="min-width: 180px;">{{ String(k).replaceAll("_", " ") }}</td>
|
||||
<td
|
||||
class="text-capitalize whitespace-break-spaces w-1/5"
|
||||
style="min-width: 180px"
|
||||
>
|
||||
{{ String(k).replaceAll('_', ' ') }}
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="overflow-hidden w-auto whitespace-normal"
|
||||
|
@ -85,6 +85,8 @@ function changeEndpoint(item: Endpoint) {
|
||||
Height: {{ baseStore.latest.block?.header.height }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- bottom-->
|
||||
<div class="px-4 py-2"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -19,171 +19,281 @@ const blockchain = useBlockchain();
|
||||
|
||||
const current = ref('');
|
||||
blockchain.$subscribe((m, s) => {
|
||||
if (current.value != s.chainName) {
|
||||
current.value = s.chainName;
|
||||
blockchain.initial();
|
||||
}
|
||||
if (current.value != s.chainName) {
|
||||
current.value = s.chainName;
|
||||
blockchain.initial();
|
||||
}
|
||||
});
|
||||
|
||||
const sidebarShow = ref(false);
|
||||
const sidebarOpen = ref(true);
|
||||
|
||||
const changeOpen = (index: Number) => {
|
||||
if (index === 0) {
|
||||
sidebarOpen.value = !sidebarOpen.value;
|
||||
}
|
||||
if (index === 0) {
|
||||
sidebarOpen.value = !sidebarOpen.value;
|
||||
}
|
||||
};
|
||||
const showDiscord = window.location.host.search('ping.pub') > -1;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-100 dark:bg-[#171d30]">
|
||||
<!-- sidebar -->
|
||||
<div class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700"
|
||||
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }">
|
||||
<div class="flex items-center pl-4 py-4 mb-1">
|
||||
<img class="w-10 h-10" src="../../assets/logo.svg" />
|
||||
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
|
||||
Ping.pub
|
||||
</h1>
|
||||
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false">
|
||||
<Icon icon="mdi-close" class="text-3xl" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
|
||||
<div v-if="item?.title && item?.children?.length" :tabindex="index" class="collapse" :class="{
|
||||
'collapse-arrow': item?.children?.length > 0,
|
||||
'collapse-open': index === 0 && sidebarOpen,
|
||||
'collapse-close': index === 0 && !sidebarOpen,
|
||||
}">
|
||||
<input type="checkbox" class="cursor-pointer" @click="changeOpen(index)" />
|
||||
<div
|
||||
class="collapse-title px-4 flex items-center h-12 cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
|
||||
'text-yellow-500': item?.title === 'Favorite',
|
||||
'text-blue-500': item?.title !== 'Favorite',
|
||||
}" />
|
||||
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
|
||||
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
|
||||
{{ item?.badgeContent }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="menu bg-base-100 w-full">
|
||||
<RouterLink v-for="(el, key) of item?.children" @click="sidebarShow = false" :key="key"
|
||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
|
||||
:to="el?.to" :class="{
|
||||
'!bg-primary':
|
||||
$route.path === el?.to?.path && item?.title !== 'Favorite',
|
||||
}">
|
||||
<Icon v-if="!el?.icon?.image" icon="mdi:chevron-right" class="mr-2 ml-3" :class="{
|
||||
'text-white': $route.path === el?.to?.path &&
|
||||
item?.title !== 'Favorite',
|
||||
}" />
|
||||
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4" />
|
||||
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
|
||||
'text-white':
|
||||
$route.path === el?.to?.path &&
|
||||
item?.title !== 'Favorite',
|
||||
}">
|
||||
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RouterLink :to="item?.to" v-if="item?.title && !item?.children?.length && item?.to"
|
||||
@click="sidebarShow = false"
|
||||
class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
|
||||
'text-yellow-500': item?.title === 'Favorite',
|
||||
'text-blue-500': item?.title !== 'Favorite',
|
||||
}" />
|
||||
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
|
||||
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
|
||||
{{ item?.badgeContent }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
<div v-if="item?.heading" class="px-4 text-sm pt-3 text-gray-400 pb-2 uppercase">
|
||||
{{ item?.heading }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">
|
||||
Sponsors
|
||||
</div>
|
||||
<a href="https://osmosis.zone"
|
||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" />
|
||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
||||
Osmosis
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://becole.com"
|
||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3" />
|
||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
||||
Becole
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">Links</div>
|
||||
<a href="https://twitter.com/ping_pub"
|
||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<Icon icon="mdi:twitter" class="text-xl mr-2" />
|
||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
||||
Twitter
|
||||
</div>
|
||||
</a>
|
||||
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW"
|
||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<Icon icon="mdi:discord" class="text-xl mr-2" />
|
||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
||||
Discord
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://github.com/ping-pub/explorer/discussions"
|
||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
||||
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
|
||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
||||
FAQ
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-[#171d30]">
|
||||
<!-- sidebar -->
|
||||
<div
|
||||
class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700"
|
||||
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"
|
||||
>
|
||||
<div class="flex items-center pl-4 py-4 mb-1">
|
||||
<img class="w-10 h-10" src="../../assets/logo.svg" />
|
||||
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
|
||||
Ping.pub
|
||||
</h1>
|
||||
<div
|
||||
class="pr-4 cursor-pointer xl:!hidden"
|
||||
@click="sidebarShow = false"
|
||||
>
|
||||
<Icon icon="mdi-close" class="text-3xl" />
|
||||
</div>
|
||||
<div class="xl:!ml-64 px-5 pt-4">
|
||||
<!-- header -->
|
||||
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow">
|
||||
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
|
||||
<Icon icon="mdi-menu" />
|
||||
</div>
|
||||
|
||||
<ChainProfile />
|
||||
|
||||
<div class="flex-1 w-0"></div>
|
||||
|
||||
<!-- <NavSearchBar />-->
|
||||
<NavBarI18n class="hidden md:!inline-block" />
|
||||
<NavbarThemeSwitcher class="!inline-block" />
|
||||
|
||||
<NavBarWallet />
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) of blockchain.computedChainMenu"
|
||||
:key="index"
|
||||
class="px-2"
|
||||
>
|
||||
<div
|
||||
v-if="item?.title && item?.children?.length"
|
||||
:tabindex="index"
|
||||
class="collapse"
|
||||
:class="{
|
||||
'collapse-arrow': item?.children?.length > 0,
|
||||
'collapse-open': index === 0 && sidebarOpen,
|
||||
'collapse-close': index === 0 && !sidebarOpen,
|
||||
}"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="cursor-pointer !h-10 block"
|
||||
@click="changeOpen(index)"
|
||||
/>
|
||||
<div
|
||||
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<Icon
|
||||
v-if="item?.icon?.icon"
|
||||
:icon="item?.icon?.icon"
|
||||
class="text-xl mr-2"
|
||||
:class="{
|
||||
'text-yellow-500': item?.title === 'Favorite',
|
||||
'text-blue-500': item?.title !== 'Favorite',
|
||||
}"
|
||||
/>
|
||||
<img
|
||||
v-if="item?.icon?.image"
|
||||
:src="item?.icon?.image"
|
||||
class="w-6 h-6 rounded-full mr-3"
|
||||
/>
|
||||
<div
|
||||
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
|
||||
<!-- 👉 Pages -->
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition mode="out-in">
|
||||
<Component :is="Component" />
|
||||
</Transition>
|
||||
</RouterView>
|
||||
|
||||
<newFooter />
|
||||
<div
|
||||
v-if="item?.badgeContent"
|
||||
class="mr-6 badge badge-sm text-white"
|
||||
:class="item?.badgeClass"
|
||||
>
|
||||
{{ item?.badgeContent }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="menu bg-base-100 w-full">
|
||||
<RouterLink
|
||||
v-for="(el, key) of item?.children"
|
||||
@click="sidebarShow = false"
|
||||
:key="key"
|
||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
|
||||
:to="el?.to"
|
||||
:class="{
|
||||
'!bg-primary':
|
||||
$route.path === el?.to?.path && item?.title !== 'Favorite',
|
||||
}"
|
||||
>
|
||||
<Icon
|
||||
v-if="!el?.icon?.image"
|
||||
icon="mdi:chevron-right"
|
||||
class="mr-2 ml-3"
|
||||
:class="{
|
||||
'text-white':
|
||||
$route.path === el?.to?.path &&
|
||||
item?.title !== 'Favorite',
|
||||
}"
|
||||
/>
|
||||
<img
|
||||
v-if="el?.icon?.image"
|
||||
:src="el?.icon?.image"
|
||||
class="w-6 h-6 rounded-full mr-3 ml-4"
|
||||
/>
|
||||
<div
|
||||
class="text-base capitalize text-gray-500 dark:text-gray-300"
|
||||
:class="{
|
||||
'text-white':
|
||||
$route.path === el?.to?.path &&
|
||||
item?.title !== 'Favorite',
|
||||
}"
|
||||
>
|
||||
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RouterLink
|
||||
:to="item?.to"
|
||||
v-if="item?.title && !item?.children?.length && item?.to"
|
||||
@click="sidebarShow = false"
|
||||
class=" cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<Icon
|
||||
v-if="item?.icon?.icon"
|
||||
:icon="item?.icon?.icon"
|
||||
class="text-xl mr-2"
|
||||
:class="{
|
||||
'text-yellow-500': item?.title === 'Favorite',
|
||||
'text-blue-500': item?.title !== 'Favorite',
|
||||
}"
|
||||
/>
|
||||
<img
|
||||
v-if="item?.icon?.image"
|
||||
:src="item?.icon?.image"
|
||||
class="w-6 h-6 rounded-full mr-3"
|
||||
/>
|
||||
<div
|
||||
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item?.badgeContent"
|
||||
class="badge badge-sm text-white"
|
||||
:class="item?.badgeClass"
|
||||
>
|
||||
{{ item?.badgeContent }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
<div
|
||||
v-if="item?.heading"
|
||||
class="px-4 text-sm text-gray-400 pb-2 uppercase"
|
||||
>
|
||||
{{ item?.heading }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
|
||||
Sponsors
|
||||
</div>
|
||||
<a
|
||||
href="https://osmosis.zone"
|
||||
target="_blank"
|
||||
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<img
|
||||
src="https://ping.pub/logos/osmosis.jpg"
|
||||
class="w-6 h-6 rounded-full mr-3"
|
||||
/>
|
||||
<div
|
||||
class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||
>
|
||||
Osmosis
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://becole.com"
|
||||
target="_blank"
|
||||
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<img
|
||||
src="https://becole.com/static/logo/logo_becole.png"
|
||||
class="w-6 h-6 rounded-full mr-3"
|
||||
/>
|
||||
<div
|
||||
class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||
>
|
||||
Becole
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">Links</div>
|
||||
<a
|
||||
href="https://twitter.com/ping_pub"
|
||||
target="_blank"
|
||||
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<Icon icon="mdi:twitter" class="text-xl mr-2" />
|
||||
<div
|
||||
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||
>
|
||||
Twitter
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
v-if="showDiscord"
|
||||
href="https://discord.com/invite/CmjYVSr6GW"
|
||||
target="_blank"
|
||||
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<Icon icon="mdi:discord" class="text-xl mr-2" />
|
||||
<div
|
||||
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||
>
|
||||
Discord
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/ping-pub/explorer/discussions"
|
||||
target="_blank"
|
||||
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
>
|
||||
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
|
||||
<div
|
||||
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||
>
|
||||
FAQ
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xl:!ml-64 px-5 pt-4">
|
||||
<!-- header -->
|
||||
<div
|
||||
class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow"
|
||||
>
|
||||
<div
|
||||
class="text-2xl pr-3 cursor-pointer xl:!hidden"
|
||||
@click="sidebarShow = true"
|
||||
>
|
||||
<Icon icon="mdi-menu" />
|
||||
</div>
|
||||
|
||||
<ChainProfile />
|
||||
|
||||
<div class="flex-1 w-0"></div>
|
||||
|
||||
<!-- <NavSearchBar />-->
|
||||
<NavBarI18n class="hidden md:!inline-block" />
|
||||
<NavbarThemeSwitcher class="!inline-block" />
|
||||
|
||||
<NavBarWallet />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Pages -->
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition mode="out-in">
|
||||
<Component :is="Component" />
|
||||
</Transition>
|
||||
</RouterView>
|
||||
|
||||
<newFooter />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -2,56 +2,61 @@
|
||||
import { ref, watch } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
|
||||
{
|
||||
label: 'English',
|
||||
i18nLang: 'en',
|
||||
},
|
||||
{
|
||||
label: '中文',
|
||||
i18nLang: 'cn',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
i18nLang: 'en',
|
||||
},
|
||||
{
|
||||
label: '中文',
|
||||
i18nLang: 'cn',
|
||||
},
|
||||
];
|
||||
|
||||
let locale = ref(useI18n({ useScope: 'global' }).locale);
|
||||
watch(locale, (val: string) => {
|
||||
document.documentElement.setAttribute('lang', val as string);
|
||||
document.documentElement.setAttribute('lang', val as string);
|
||||
});
|
||||
|
||||
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
||||
|
||||
watch(currentLang, (val: string) => {
|
||||
document.documentElement.setAttribute('lang', val as string);
|
||||
document.documentElement.setAttribute('lang', val as string);
|
||||
});
|
||||
|
||||
const handleLangChange = (lang: string) => {
|
||||
locale.value = lang;
|
||||
currentLang.value = lang;
|
||||
localStorage.setItem('lang', lang);
|
||||
locale.value = lang;
|
||||
currentLang.value = lang;
|
||||
localStorage.setItem('lang', lang);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<Icon icon="mdi-translate" class="text-2xl" />
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
||||
<div
|
||||
class="dropdown"
|
||||
:class="
|
||||
currentLang === 'ar'
|
||||
? 'dropdown-right'
|
||||
: 'dropdown-bottom dropdown-end'
|
||||
"
|
||||
>
|
||||
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
|
||||
<a
|
||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
||||
@click="handleLangChange(lang.i18nLang)"
|
||||
>{{ lang.label }}</a
|
||||
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
||||
<Icon
|
||||
icon="mdi-translate"
|
||||
class="text-2xl text-gray-500 dark:text-gray-400"
|
||||
/>
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
|
||||
<a
|
||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
||||
@click="handleLangChange(lang.i18nLang)"
|
||||
>{{ lang.label }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, } from 'vue';
|
||||
|
||||
const walletStore = useWalletStore();
|
||||
const chainStore = useBlockchain();
|
||||
const baseStore = useBaseStore();
|
||||
@ -37,7 +38,7 @@ const tipMsg = computed(() => {
|
||||
<div class="dropdown dropdown-hover dropdown-end">
|
||||
<label
|
||||
tabindex="0"
|
||||
class="btn btn-sm m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
|
||||
class="btn btn-sm btn-primary m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
|
||||
>
|
||||
<Icon icon="mdi:wallet" />
|
||||
<span class="ml-1 hidden md:block">
|
||||
@ -51,7 +52,7 @@ const tipMsg = computed(() => {
|
||||
<label
|
||||
v-if="!walletStore?.currentAddress"
|
||||
for="PingConnectWallet"
|
||||
class="btn btn-sm"
|
||||
class="btn btn-sm btn-primary"
|
||||
><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block"
|
||||
>Connect Wallet</span
|
||||
></label
|
||||
@ -108,6 +109,7 @@ const tipMsg = computed(() => {
|
||||
:chain-id="baseStore.currentChainId"
|
||||
:hd-path="chainStore.defaultHDPath"
|
||||
@connect="walletStateChange"
|
||||
@keplr-config="walletStore.suggestChain()"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
@ -1,45 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, computed } from 'vue';
|
||||
import { useBaseStore } from '@/stores';
|
||||
|
||||
const themeMap: Record<string, string> = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
|
||||
const themeMap: Record<string, string> = {
|
||||
system: 'mdi-laptop',
|
||||
light: 'mdi-weather-sunny',
|
||||
dark: 'mdi-weather-night',
|
||||
};
|
||||
const baseStore = useBaseStore();
|
||||
const theme = computed(() => {
|
||||
return baseStore.theme;
|
||||
});
|
||||
|
||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
||||
|
||||
const changeMode = (val?: string) => {
|
||||
let value = 'dark';
|
||||
const currentValue = val || theme.value;
|
||||
if (currentValue === 'dark') {
|
||||
value = 'light';
|
||||
}
|
||||
if (
|
||||
currentValue === 'system' &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
) {
|
||||
value = 'dark';
|
||||
}
|
||||
if (value === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.remove('light');
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', value);
|
||||
window.localStorage.setItem('theme', value);
|
||||
theme.value = value;
|
||||
const changeMode = (val?: 'dark' | 'light') => {
|
||||
let value: 'dark' | 'light' = 'dark';
|
||||
const currentValue: 'dark' | 'light' = val || theme.value;
|
||||
if (currentValue === 'dark') {
|
||||
value = 'light';
|
||||
}
|
||||
if (value === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.remove('light');
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', value);
|
||||
window.localStorage.setItem('theme', value);
|
||||
baseStore.theme = value;
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
||||
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tooltip tooltip-bottom delay-1000">
|
||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
|
||||
<Icon :icon="themeMap?.[theme]" class="text-2xl" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="tooltip tooltip-bottom delay-1000">
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-sm mx-1"
|
||||
@click="changeMode()"
|
||||
>
|
||||
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -2,9 +2,9 @@
|
||||
import { useWasmStore } from '../WasmStore';
|
||||
import { ref } from 'vue';
|
||||
import type {
|
||||
ContractInfo,
|
||||
PaginabledContractStates,
|
||||
PaginabledContracts,
|
||||
ContractInfo,
|
||||
PaginabledContractStates,
|
||||
PaginabledContracts,
|
||||
} from '../types';
|
||||
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
||||
import { useFormatter, useTxDialog } from '@/stores';
|
||||
@ -13,20 +13,20 @@ import { PageRequest } from '@/types';
|
||||
|
||||
const props = defineProps(['code_id', 'chain']);
|
||||
|
||||
const pageRequest = ref(new PageRequest())
|
||||
const pageRequest = ref(new PageRequest());
|
||||
const response = ref({} as PaginabledContracts);
|
||||
|
||||
const wasmStore = useWasmStore();
|
||||
function loadContract(pageNum: number) {
|
||||
const pr = new PageRequest()
|
||||
pr.setPage(pageNum)
|
||||
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
|
||||
response.value = x;
|
||||
});
|
||||
const pr = new PageRequest();
|
||||
pr.setPage(pageNum);
|
||||
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
|
||||
response.value = x;
|
||||
});
|
||||
}
|
||||
loadContract(1)
|
||||
loadContract(1);
|
||||
|
||||
const dialog = useTxDialog()
|
||||
const dialog = useTxDialog();
|
||||
const format = useFormatter();
|
||||
const infoDialog = ref(false);
|
||||
const info = ref({} as ContractInfo);
|
||||
@ -34,66 +34,68 @@ const state = ref({} as PaginabledContractStates);
|
||||
const selected = ref('');
|
||||
|
||||
function showInfo(address: string) {
|
||||
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
|
||||
info.value = x.contract_info;
|
||||
});
|
||||
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
|
||||
info.value = x.contract_info;
|
||||
});
|
||||
}
|
||||
function showState(address: string) {
|
||||
selected.value = address
|
||||
pageload(1)
|
||||
selected.value = address;
|
||||
pageload(1);
|
||||
}
|
||||
|
||||
function pageload(p: number) {
|
||||
pageRequest.value.setPage(p)
|
||||
wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => {
|
||||
state.value = x;
|
||||
pageRequest.value.setPage(p);
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractStates(selected.value, pageRequest.value)
|
||||
.then((x) => {
|
||||
state.value = x;
|
||||
});
|
||||
}
|
||||
|
||||
function showQuery(address: string) {
|
||||
selected.value = address;
|
||||
query.value = '';
|
||||
result.value = '';
|
||||
selected.value = address;
|
||||
query.value = '';
|
||||
result.value = '';
|
||||
}
|
||||
|
||||
function queryContract() {
|
||||
try {
|
||||
if (selectedRadio.value === 'raw') {
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractRawQuery(selected.value, query.value)
|
||||
.then((x) => {
|
||||
result.value = JSON.stringify(x);
|
||||
})
|
||||
.catch((err) => {
|
||||
result.value = JSON.stringify(err);
|
||||
});
|
||||
} else {
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractSmartQuery(selected.value, query.value)
|
||||
.then((x) => {
|
||||
result.value = JSON.stringify(x);
|
||||
})
|
||||
.catch((err) => {
|
||||
result.value = JSON.stringify(err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
result.value = JSON.stringify(err); // not works for now
|
||||
try {
|
||||
if (selectedRadio.value === 'raw') {
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractRawQuery(selected.value, query.value)
|
||||
.then((x) => {
|
||||
result.value = JSON.stringify(x);
|
||||
})
|
||||
.catch((err) => {
|
||||
result.value = JSON.stringify(err);
|
||||
});
|
||||
} else {
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractSmartQuery(selected.value, query.value)
|
||||
.then((x) => {
|
||||
result.value = JSON.stringify(x);
|
||||
})
|
||||
.catch((err) => {
|
||||
result.value = JSON.stringify(err);
|
||||
});
|
||||
}
|
||||
// TODO, show error in the result.
|
||||
} catch (err) {
|
||||
result.value = JSON.stringify(err); // not works for now
|
||||
}
|
||||
// TODO, show error in the result.
|
||||
}
|
||||
|
||||
const radioContent = [
|
||||
{
|
||||
title: 'Raw Query',
|
||||
desc: 'Return raw result',
|
||||
value: 'raw',
|
||||
},
|
||||
{
|
||||
title: 'Smart Query',
|
||||
desc: 'Return structure result if possible',
|
||||
value: 'smart',
|
||||
},
|
||||
{
|
||||
title: 'Raw Query',
|
||||
desc: 'Return raw result',
|
||||
value: 'raw',
|
||||
},
|
||||
{
|
||||
title: 'Smart Query',
|
||||
desc: 'Return structure result if possible',
|
||||
value: 'smart',
|
||||
},
|
||||
];
|
||||
|
||||
const selectedRadio = ref('raw');
|
||||
@ -101,130 +103,193 @@ const query = ref('');
|
||||
const result = ref('');
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||
<h2 class="card-title truncate w-full">
|
||||
Contract List of Code: {{ props.code_id }}
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-compact w-full mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="position: relative; z-index: 2">Contract List</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(v, index) in response.contracts" :key="index" class="hover">
|
||||
<td>{{ v }}</td>
|
||||
<td>
|
||||
<label @click="showInfo(v)" for="modal-contract-detail"
|
||||
class="btn btn-primary btn-xs text-xs mr-2">contract</label>
|
||||
<div>
|
||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||
<h2 class="card-title truncate w-full">
|
||||
Contract List of Code: {{ props.code_id }}
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-compact w-full mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="position: relative; z-index: 2">Contract List</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(v, index) in response.contracts"
|
||||
:key="index"
|
||||
class="hover"
|
||||
>
|
||||
<td>{{ v }}</td>
|
||||
<td>
|
||||
<label
|
||||
@click="showInfo(v)"
|
||||
for="modal-contract-detail"
|
||||
class="btn btn-primary btn-xs text-xs mr-2"
|
||||
>contract</label
|
||||
>
|
||||
|
||||
<label class="btn btn-primary btn-xs text-xs mr-2" for="modal-contract-states"
|
||||
@click="showState(v)">
|
||||
States
|
||||
</label>
|
||||
<label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2"
|
||||
@click="showQuery(v)">
|
||||
Query
|
||||
</label>
|
||||
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs"
|
||||
@click="dialog.open('wasm_execute_contract', { contract: v })">
|
||||
Execute
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="flex justify-between">
|
||||
<PaginationBar :limit="50" :total="response.pagination?.total" :callback="loadContract" />
|
||||
<label for="wasm_instantiate_contract" class="btn btn-primary my-5"
|
||||
@click="dialog.open('wasm_instantiate_contract', { codeId: props.code_id })">Instantiate
|
||||
Contract</label>
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="btn btn-primary btn-xs text-xs mr-2"
|
||||
for="modal-contract-states"
|
||||
@click="showState(v)"
|
||||
>
|
||||
States
|
||||
</label>
|
||||
<label
|
||||
for="modal-contract-query"
|
||||
class="btn btn-primary btn-xs text-xs mr-2"
|
||||
@click="showQuery(v)"
|
||||
>
|
||||
Query
|
||||
</label>
|
||||
<label
|
||||
for="wasm_execute_contract"
|
||||
class="btn btn-primary btn-xs text-xs"
|
||||
@click="dialog.open('wasm_execute_contract', { contract: v })"
|
||||
>
|
||||
Execute
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="flex justify-between">
|
||||
<PaginationBar
|
||||
:limit="50"
|
||||
:total="response.pagination?.total"
|
||||
:callback="loadContract"
|
||||
/>
|
||||
<label
|
||||
for="wasm_instantiate_contract"
|
||||
class="btn btn-primary my-5"
|
||||
@click="
|
||||
dialog.open('wasm_instantiate_contract', {
|
||||
codeId: props.code_id,
|
||||
})
|
||||
"
|
||||
>Instantiate Contract</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
|
||||
<label for="modal-contract-states" class="modal cursor-pointer">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<div>
|
||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||
<div class="text-lg">Contract States</div>
|
||||
<label @click="infoDialog = false" for="modal-contract-states"
|
||||
class="btn btn-sm btn-circle">✕</label>
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<table class="table table-compact w-full text-sm">
|
||||
<tr v-for="(v, index) in state.models" :key="index" class="hover">
|
||||
<td class="" :data-tip="format.hexToString(v.key)">
|
||||
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
|
||||
</td>
|
||||
<td class="text-left w-3/4" :title="format.base64ToString(v.value)">
|
||||
{{ format.base64ToString(v.value) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageload" />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
|
||||
<label for="modal-contract-query" class="modal cursor-pointer">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<div>
|
||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||
<div class="text-lg font-semibold">Query Contract</div>
|
||||
<label @click="infoDialog = false" for="modal-contract-query"
|
||||
class="btn btn-sm btn-circle">✕</label>
|
||||
</div>
|
||||
<div class="px-3">
|
||||
<div>
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent"
|
||||
:key="index" :class="{ 'pt-2': index === 0 }">
|
||||
<label class="label cursor-pointer justify-start" @click="selectedRadio = item?.value">
|
||||
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4"
|
||||
:checked="item?.value === selectedRadio" style="border: 1px solid #d2d6dc" />
|
||||
<div>
|
||||
<div class="text-base font-semibold">
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
<div class="text-xs">{{ item?.desc }}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VTextarea v-model="query" label="Query String" class="my-2" />
|
||||
<VTextarea v-model="result" label="Result" />
|
||||
</div>
|
||||
<div class="mt-4 mb-4">
|
||||
<button class="btn !btn-yes !border-yes px-4 text-white" @click="queryContract()">
|
||||
Query Contract
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<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>
|
||||
</div></template>
|
||||
|
||||
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
|
||||
<label for="modal-contract-states" class="modal cursor-pointer">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<div>
|
||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||
<div class="text-lg">Contract States</div>
|
||||
<label
|
||||
@click="infoDialog = false"
|
||||
for="modal-contract-states"
|
||||
class="btn btn-sm btn-circle"
|
||||
>✕</label
|
||||
>
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<table class="table table-compact w-full text-sm">
|
||||
<tr v-for="(v, index) in state.models" :key="index" class="hover">
|
||||
<td class="" :data-tip="format.hexToString(v.key)">
|
||||
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
|
||||
</td>
|
||||
<td
|
||||
class="text-left w-3/4"
|
||||
:title="format.base64ToString(v.value)"
|
||||
>
|
||||
{{ format.base64ToString(v.value) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<PaginationBar
|
||||
:limit="pageRequest.limit"
|
||||
:total="state.pagination?.total"
|
||||
:callback="pageload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
|
||||
<label for="modal-contract-query" class="modal cursor-pointer">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<div>
|
||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||
<div class="text-lg font-semibold">Query Contract</div>
|
||||
<label
|
||||
@click="infoDialog = false"
|
||||
for="modal-contract-query"
|
||||
class="btn btn-sm btn-circle"
|
||||
>✕</label
|
||||
>
|
||||
</div>
|
||||
<div class="px-3">
|
||||
<div>
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div
|
||||
class="form-control border rounded px-4"
|
||||
v-for="(item, index) of radioContent"
|
||||
:key="index"
|
||||
:class="{ 'pt-2': index === 0 }"
|
||||
>
|
||||
<label
|
||||
class="label cursor-pointer justify-start"
|
||||
@click="selectedRadio = item?.value"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="radio-10"
|
||||
class="radio radio-sm radio-primary mr-4"
|
||||
:checked="item?.value === selectedRadio"
|
||||
style="border: 1px solid #d2d6dc"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-base font-semibold">
|
||||
{{ item?.title }}
|
||||
</div>
|
||||
<div class="text-xs">{{ item?.desc }}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VTextarea v-model="query" label="Query String" class="my-2" />
|
||||
<VTextarea v-model="result" label="Result" />
|
||||
</div>
|
||||
<div class="mt-4 mb-4">
|
||||
<button
|
||||
class="btn !btn-yes !border-yes px-4 text-white"
|
||||
@click="queryContract()"
|
||||
>
|
||||
Query Contract
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -321,19 +321,21 @@ const color = computed(() => {
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-100 rounded mt-4 shadow">
|
||||
<div class="bg-base-100 rounded mt-4">
|
||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||
Application Versions
|
||||
</div>
|
||||
<!-- Application Version -->
|
||||
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
|
||||
<div class="h-4"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4 shadow">
|
||||
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4">
|
||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||
Node Information
|
||||
</div>
|
||||
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
|
||||
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
|
||||
<div class="h-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -45,7 +45,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Node Information -->
|
||||
<div class="bg-base-100 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>
|
||||
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
useBaseStore,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useTxDialog,
|
||||
useBaseStore,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useTxDialog,
|
||||
} from '@/stores';
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { onMounted, ref, type DebuggerEvent } from 'vue';
|
||||
@ -22,319 +22,343 @@ const tab = ref('active');
|
||||
const unbondList = ref([] as Validator[]);
|
||||
const base = useBaseStore();
|
||||
onMounted(() => {
|
||||
staking.fetchInacitveValdiators().then((x) => {
|
||||
unbondList.value = x;
|
||||
});
|
||||
staking.fetchInacitveValdiators().then((x) => {
|
||||
unbondList.value = x;
|
||||
});
|
||||
});
|
||||
|
||||
async function fetchChange() {
|
||||
let page = 0;
|
||||
let page = 0;
|
||||
|
||||
let height = Number(base.latest?.block?.header?.height || 0);
|
||||
if (height > 14400) {
|
||||
height -= 14400;
|
||||
} else {
|
||||
height = 1;
|
||||
}
|
||||
// voting power in 24h ago
|
||||
while (page < staking.validators.length && height > 0) {
|
||||
await base.fetchValidatorByHeight(height, page).then((x) => {
|
||||
x.validators.forEach((v) => {
|
||||
yesterday.value[v.pub_key.key] = Number(v.voting_power);
|
||||
});
|
||||
});
|
||||
page += 100;
|
||||
}
|
||||
let height = Number(base.latest?.block?.header?.height || 0);
|
||||
if (height > 14400) {
|
||||
height -= 14400;
|
||||
} else {
|
||||
height = 1;
|
||||
}
|
||||
// voting power in 24h ago
|
||||
while (page < staking.validators.length && height > 0) {
|
||||
await base.fetchValidatorByHeight(height, page).then((x) => {
|
||||
x.validators.forEach((v) => {
|
||||
yesterday.value[v.pub_key.key] = Number(v.voting_power);
|
||||
});
|
||||
});
|
||||
page += 100;
|
||||
}
|
||||
|
||||
page = 0;
|
||||
// voting power for now
|
||||
while (page < staking.validators.length) {
|
||||
await base.fetchLatestValidators(page).then((x) => {
|
||||
x.validators.forEach((v) => {
|
||||
latest.value[v.pub_key.key] = Number(v.voting_power);
|
||||
});
|
||||
});
|
||||
page += 100;
|
||||
}
|
||||
page = 0;
|
||||
// voting power for now
|
||||
while (page < staking.validators.length) {
|
||||
await base.fetchLatestValidators(page).then((x) => {
|
||||
x.validators.forEach((v) => {
|
||||
latest.value[v.pub_key.key] = Number(v.voting_power);
|
||||
});
|
||||
});
|
||||
page += 100;
|
||||
}
|
||||
}
|
||||
|
||||
const changes = computed(() => {
|
||||
const changes = {} as Record<string, number>;
|
||||
Object.keys(latest.value).forEach((k) => {
|
||||
const l = latest.value[k] || 0;
|
||||
const y = yesterday.value[k] || 0;
|
||||
changes[k] = l - y;
|
||||
});
|
||||
return changes;
|
||||
const changes = {} as Record<string, number>;
|
||||
Object.keys(latest.value).forEach((k) => {
|
||||
const l = latest.value[k] || 0;
|
||||
const y = yesterday.value[k] || 0;
|
||||
changes[k] = l - y;
|
||||
});
|
||||
return changes;
|
||||
});
|
||||
|
||||
const change24 = (key: Key) => {
|
||||
const txt = key.key;
|
||||
// const n: number = latest.value[txt];
|
||||
// const o: number = yesterday.value[txt];
|
||||
// // console.log( txt, n, o)
|
||||
// return n > 0 && o > 0 ? n - o : 0;
|
||||
return changes.value[txt];
|
||||
const txt = key.key;
|
||||
// const n: number = latest.value[txt];
|
||||
// const o: number = yesterday.value[txt];
|
||||
// // console.log( txt, n, o)
|
||||
// return n > 0 && o > 0 ? n - o : 0;
|
||||
return changes.value[txt];
|
||||
};
|
||||
|
||||
const change24Text = (key?: Key) => {
|
||||
if (!key) return '';
|
||||
const v = change24(key);
|
||||
return v && v !== 0 ? format.showChanges(v) : '';
|
||||
if (!key) return '';
|
||||
const v = change24(key);
|
||||
return v && v !== 0 ? format.showChanges(v) : '';
|
||||
};
|
||||
|
||||
const change24Color = (key?: Key) => {
|
||||
if (!key) return '';
|
||||
const v = change24(key);
|
||||
if (v > 0) return 'text-success';
|
||||
if (v < 0) return 'text-error';
|
||||
if (!key) return '';
|
||||
const v = change24(key);
|
||||
if (v > 0) return 'text-success';
|
||||
if (v < 0) return 'text-error';
|
||||
};
|
||||
|
||||
const list = computed(() => {
|
||||
return tab.value === 'active' ? staking.validators : unbondList.value;
|
||||
// return staking.validators
|
||||
return tab.value === 'active' ? staking.validators : unbondList.value;
|
||||
// return staking.validators
|
||||
});
|
||||
|
||||
const loadAvatars = () => {
|
||||
// fetch avatar from keybase
|
||||
let promise = Promise.resolve();
|
||||
staking.validators.forEach((item) => {
|
||||
promise = promise.then(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const identity = item.description?.identity;
|
||||
if (identity && !avatars.value[identity]) {
|
||||
staking.keybase(identity).then((d) => {
|
||||
if (Array.isArray(d.them) && d.them.length > 0) {
|
||||
const uri = String(d.them[0]?.pictures?.primary?.url).replace(
|
||||
'https://s3.amazonaws.com/keybase_processed_uploads/',
|
||||
''
|
||||
);
|
||||
if (uri) {
|
||||
avatars.value[identity] = uri;
|
||||
localStorage.setItem(
|
||||
'avatars',
|
||||
JSON.stringify(avatars.value)
|
||||
);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
// fetch avatar from keybase
|
||||
let promise = Promise.resolve();
|
||||
staking.validators.forEach((item) => {
|
||||
promise = promise.then(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const identity = item.description?.identity;
|
||||
if (identity && !avatars.value[identity]) {
|
||||
staking.keybase(identity).then((d) => {
|
||||
if (Array.isArray(d.them) && d.them.length > 0) {
|
||||
const uri = String(
|
||||
d.them[0]?.pictures?.primary?.url
|
||||
).replace(
|
||||
'https://s3.amazonaws.com/keybase_processed_uploads/',
|
||||
''
|
||||
);
|
||||
if (uri) {
|
||||
avatars.value[identity] = uri;
|
||||
localStorage.setItem(
|
||||
'avatars',
|
||||
JSON.stringify(avatars.value)
|
||||
);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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}`;
|
||||
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;
|
||||
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';
|
||||
}
|
||||
switch (true) {
|
||||
case tab.value === 'active' && percent < 0.33:
|
||||
return 'error';
|
||||
case tab.value === 'active' && percent < 0.67:
|
||||
return 'warning';
|
||||
default:
|
||||
return 'primary';
|
||||
}
|
||||
};
|
||||
|
||||
fetchChange();
|
||||
loadAvatars();
|
||||
|
||||
</script>
|
||||
<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 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)}`"
|
||||
<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
|
||||
>
|
||||
<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"
|
||||
<a
|
||||
class="tab text-gray-400"
|
||||
:class="{ 'tab-active': tab === 'inactive' }"
|
||||
@click="tab = 'inactive'"
|
||||
>Inactive</a
|
||||
>
|
||||
<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>
|
||||
|
||||
<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 rounded bg-primary capitalize border-none"
|
||||
@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 class="text-lg font-semibold">
|
||||
{{ list.length }}/{{ staking.params.max_validators }}
|
||||
</div>
|
||||
</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 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
|
||||
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>
|
||||
</template>
|
||||
|
||||
<route>
|
||||
@ -348,7 +372,7 @@ loadAvatars();
|
||||
|
||||
<style>
|
||||
.staking-table.table :where(th, td) {
|
||||
padding: 8px 5px;
|
||||
background: transparent;
|
||||
padding: 8px 5px;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
|
@ -128,7 +128,7 @@ function changeTab(v: string) {
|
||||
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
|
||||
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-x-4 mt-4" :class="tab === '2' ? '' : 'hidden'">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4" :class="tab === '2' ? '' : 'hidden'">
|
||||
<div v-for="({ v, signing, hex }, i) in list" :key="i">
|
||||
<div class="flex items-center justify-between py-0">
|
||||
<label class="truncate text-sm">
|
||||
|
@ -2,15 +2,17 @@
|
||||
import { CosmosRestClient } from '@/libs/client';
|
||||
import { useDashboard, useFormatter } from '@/stores';
|
||||
import type { Coin, Delegation } from '@/types';
|
||||
import { fromBech32, toBase64, toBech32 } from '@cosmjs/encoding';
|
||||
import { fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { computed } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts } from './utils';
|
||||
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts, type LocalKey } from './utils';
|
||||
|
||||
const dashboard = useDashboard()
|
||||
const format = useFormatter()
|
||||
const editable = ref(false) // to edit addresses
|
||||
const sourceAddress = ref('') //
|
||||
const selectedSource = ref({} as LocalKey) //
|
||||
function toggleEdit() {
|
||||
editable.value = !editable.value
|
||||
}
|
||||
@ -19,42 +21,21 @@ const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}")
|
||||
const balances = ref({} as Record<string, Coin[]>)
|
||||
const delegations = ref({} as Record<string, Delegation[]>)
|
||||
|
||||
scanLocalKeys().forEach(wallet => {
|
||||
const { data } = fromBech32(wallet.cosmosAddress)
|
||||
const walletKey = toBase64(data)
|
||||
let imported = conf.value[walletKey]
|
||||
// save the default address to local storage
|
||||
if (!imported) {
|
||||
imported = []
|
||||
dashboard.favorite.forEach(x => {
|
||||
const chain = dashboard.chains[x]
|
||||
if (chain && wallet.hdPath.indexOf(chain.coinType) === 6) {
|
||||
imported.push({
|
||||
chainName: chain.chainName,
|
||||
logo: chain.logo,
|
||||
address: toBech32(chain.bech32Prefix, data),
|
||||
coinType: chain.coinType,
|
||||
endpoint: chain.endpoints.rest?.at(0)?.address
|
||||
})
|
||||
// load balances
|
||||
Object.values(conf.value).forEach(imported => {
|
||||
let promise = Promise.resolve()
|
||||
for (let i = 0; i < imported.length; i++) {
|
||||
promise = promise.then(() => new Promise((resolve) => {
|
||||
// continue only if the page is living
|
||||
if (imported[i].endpoint) {
|
||||
loadBalances(imported[i].endpoint || "", imported[i].address).finally(() => resolve())
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
conf.value[walletKey] = imported;
|
||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
||||
}))
|
||||
}
|
||||
// load balance & delegations
|
||||
imported.forEach(x => {
|
||||
if (x.endpoint) {
|
||||
const client = CosmosRestClient.newDefault(x.endpoint)
|
||||
client.getBankBalances(x.address).then(res => {
|
||||
balances.value[x.address] = res.balances.filter(x => x.denom.length < 10)
|
||||
})
|
||||
client.getStakingDelegations(x.address).then(res => {
|
||||
delegations.value[x.address] = res.delegation_responses
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
const accounts = computed(() => {
|
||||
let a = [] as AccountEntry[]
|
||||
@ -69,6 +50,8 @@ const accounts = computed(() => {
|
||||
denom = b.balance.denom
|
||||
})
|
||||
entry.delegation = { amount: String(amount), denom }
|
||||
} else {
|
||||
entry.delegation = undefined
|
||||
}
|
||||
entry.balances = balances.value[entry.address]
|
||||
})
|
||||
@ -81,43 +64,97 @@ const addresses = computed(() => {
|
||||
return accounts.value.map(x => (x.address))
|
||||
})
|
||||
|
||||
const sourceOptions = computed(() => {
|
||||
// scan all connected wallet
|
||||
const keys = scanLocalKeys()
|
||||
// all existed keys
|
||||
Object.values(conf.value).forEach(x => {
|
||||
const [first] = x
|
||||
if (first) {
|
||||
const { data } = fromBech32(first.address)
|
||||
const hex = toHex(data)
|
||||
if (keys.findIndex(k => toHex(fromBech32(k.cosmosAddress).data) === hex) === -1) {
|
||||
keys.push({
|
||||
cosmosAddress: first.address,
|
||||
hdPath: `m/44/${first.coinType}/0'/0/0`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// address
|
||||
if (sourceAddress.value) {
|
||||
const { prefix, data } = fromBech32(sourceAddress.value)
|
||||
const chain = Object.values(dashboard.chains).find(x => x.bech32Prefix === prefix)
|
||||
if (chain) {
|
||||
keys.push({
|
||||
cosmosAddress: sourceAddress.value,
|
||||
hdPath: `m/44/${chain.coinType}/0'/0/0`
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!selectedSource.value.cosmosAddress && keys.length > 0) {
|
||||
selectedSource.value = keys[0]
|
||||
}
|
||||
return keys
|
||||
})
|
||||
|
||||
const availableAccount = computed(() => {
|
||||
return scanCompatibleAccounts().filter(x => !addresses.value.includes(x.address))
|
||||
if (selectedSource.value.cosmosAddress) {
|
||||
return scanCompatibleAccounts([selectedSource.value]).filter(x => !addresses.value.includes(x.address))
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
function removeAddress(addr: string) {
|
||||
const newConf = {} as Record<string, AccountEntry[]>
|
||||
Object.keys(conf.value).forEach(key => {
|
||||
newConf[key] = conf.value[key].filter(x => x.address !== addr)
|
||||
const acc = conf.value[key].filter(x => x.address !== addr)
|
||||
if (acc.length > 0) newConf[key] = acc
|
||||
})
|
||||
conf.value = newConf
|
||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
||||
}
|
||||
|
||||
async function addAddress(acc: AccountEntry) {
|
||||
console.log('add', acc)
|
||||
const {data} = fromBech32(acc.address)
|
||||
const { data } = fromBech32(acc.address)
|
||||
const key = toBase64(data)
|
||||
|
||||
if(conf.value[key]) {
|
||||
if (conf.value[key]) {
|
||||
// existed
|
||||
if (conf.value[key].findIndex(x => x.address === acc.address) > -1) {
|
||||
return
|
||||
}
|
||||
conf.value[key].push(acc)
|
||||
} else {
|
||||
conf.value[key] = [acc]
|
||||
}
|
||||
|
||||
if(acc.endpoint) {
|
||||
const client = CosmosRestClient.newDefault(acc.endpoint)
|
||||
client.getBankBalances(acc.address).then(res => {
|
||||
balances.value[acc.address] = res.balances.filter(x => x.denom.length < 10)
|
||||
})
|
||||
client.getStakingDelegations(acc.address).then(res => {
|
||||
delegations.value[acc.address] = res.delegation_responses
|
||||
})
|
||||
// also add chain to favorite
|
||||
if (!dashboard?.favoriteMap?.[acc.chainName]) {
|
||||
dashboard.favoriteMap[acc.chainName] = true
|
||||
window.localStorage.setItem(
|
||||
'favoriteMap',
|
||||
JSON.stringify(dashboard.favoriteMap)
|
||||
);
|
||||
}
|
||||
|
||||
if (acc.endpoint) {
|
||||
loadBalances(acc.endpoint, acc.address)
|
||||
}
|
||||
|
||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
||||
}
|
||||
|
||||
async function loadBalances(endpoint: string, address: string) {
|
||||
const client = CosmosRestClient.newDefault(endpoint)
|
||||
await client.getBankBalances(address).then(res => {
|
||||
balances.value[address] = res.balances.filter(x => x.denom.length < 10)
|
||||
})
|
||||
await client.getStakingDelegations(address).then(res => {
|
||||
delegations.value[address] = res.delegation_responses
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
@ -140,38 +177,13 @@ async function addAddress(acc: AccountEntry) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex lg:!ml-4 lg:!mt-0">
|
||||
<span class="hidden sm:!block">
|
||||
<a href="#address-modal"
|
||||
class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z" />
|
||||
<path
|
||||
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z" />
|
||||
</svg>
|
||||
Import
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span class="ml-3 hidden sm:!block">
|
||||
<button type="button"
|
||||
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
|
||||
@click="toggleEdit">
|
||||
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table w-full">
|
||||
|
||||
<table class="table table-compact w-full">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<thead class="rounded-none">
|
||||
<tr>
|
||||
<th v-if="editable"></th>
|
||||
<th>Account</th>
|
||||
@ -186,11 +198,11 @@ async function addAddress(acc: AccountEntry) {
|
||||
<td v-if="editable">
|
||||
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
|
||||
</td>
|
||||
<td>
|
||||
<td class="px-4">
|
||||
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="avatar">
|
||||
<div class="mask mask-squircle w-12 h-12">
|
||||
<div class="mask mask-squircle w-8 h-8">
|
||||
<img :src="acc.logo" :alt="acc.address" />
|
||||
</div>
|
||||
</div>
|
||||
@ -224,7 +236,33 @@ async function addAddress(acc: AccountEntry) {
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<th colspan="10">
|
||||
<RouterLink to="/wallet/keplr"> Add chain to Keplr </RouterLink>
|
||||
<div class="flex justify-between">
|
||||
<span class="hidden sm:!block">
|
||||
<button type="button"
|
||||
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
|
||||
@click="toggleEdit">
|
||||
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
<a href="#address-modal"
|
||||
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z" />
|
||||
<path
|
||||
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z" />
|
||||
</svg>
|
||||
Import
|
||||
</a>
|
||||
</span>
|
||||
<RouterLink to="/wallet/keplr" class="btn btn-sm"> Add chain to Keplr </RouterLink>
|
||||
</div>
|
||||
</th>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -232,45 +270,57 @@ async function addAddress(acc: AccountEntry) {
|
||||
<!-- Put this part before </body> tag -->
|
||||
<div class="modal" id="address-modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">Import Accounts
|
||||
<div class="dropdown dropdown-hover">
|
||||
<label tabindex="0" class="text-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-4 h-4 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<h3 class="font-bold text-lg mb-2">Derive Account From Address
|
||||
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<label class="input-group input-group-sm w-full">
|
||||
<span>Connected</span>
|
||||
<select v-model="selectedSource" class="select select-bordered select-sm w-3/4">
|
||||
<option v-for="source in sourceOptions" :value="source">
|
||||
<span class=" overflow-hidden">{{ source.cosmosAddress }}</span>
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="input-group input-group-sm my-2">
|
||||
<span>Custom</span>
|
||||
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" />
|
||||
</label>
|
||||
<div tabindex="0" class="card compact dropdown-content dark:bg-info-content bg-slate-300 shadow rounded-box w-64">
|
||||
<div class="card-body">
|
||||
<p>Only shows blockchains on your favorite list</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h3>
|
||||
<p class="py-4 max-h-60 overflow-y-auto">
|
||||
<table>
|
||||
<tr v-for="acc in availableAccount" >
|
||||
<td>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="py-4 max-h-72 overflow-y-auto">
|
||||
<table class="table table-compact">
|
||||
<tr v-for="acc in availableAccount">
|
||||
<td>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="avatar">
|
||||
<div class="mask mask-squircle w-8 h-8">
|
||||
<img :src="acc.logo" :alt="acc.address" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold capitalize">{{ acc.chainName }}</div>
|
||||
<div class="tooltip" :class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'"
|
||||
:data-tip="`Coin Type: ${acc.coinType}`">
|
||||
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'">
|
||||
{{ acc.chainName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs opacity-50 hidden md:!block">{{ acc.address }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)">
|
||||
<Icon icon="mdi:plus"/>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<a href="#" class="btn">Close</a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)">
|
||||
<Icon icon="mdi:plus" />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-action mt-2 mb-0">
|
||||
<a href="#" class="btn btn-primary btn-sm">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></template>
|
||||
</template>
|
||||
|
@ -10,39 +10,42 @@ export interface AccountEntry {
|
||||
endpoint?: string,
|
||||
delegation?: Coin,
|
||||
balances?: Coin[],
|
||||
compatiable?: boolean,
|
||||
}
|
||||
|
||||
export interface LocalKey {
|
||||
cosmosAddress: string, hdPath: string
|
||||
}
|
||||
|
||||
export function scanLocalKeys() {
|
||||
const connected = [] as {cosmosAddress: string, hdPath: string}[]
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key?.startsWith("m/44")) {
|
||||
const wallet = JSON.parse(localStorage.getItem(key) || "")
|
||||
if (wallet) {
|
||||
connected.push(wallet)
|
||||
}
|
||||
const connected = [] as LocalKey[]
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key?.startsWith("m/44")) {
|
||||
const wallet = JSON.parse(localStorage.getItem(key) || "")
|
||||
if (wallet) {
|
||||
connected.push(wallet)
|
||||
}
|
||||
}
|
||||
return connected
|
||||
}
|
||||
return connected
|
||||
}
|
||||
|
||||
|
||||
export function scanCompatibleAccounts() {
|
||||
const dashboard = useDashboard()
|
||||
const available = [] as AccountEntry[]
|
||||
scanLocalKeys().forEach(wallet => {
|
||||
Object.values(dashboard.chains).forEach(chain => {
|
||||
if (wallet.hdPath.indexOf(chain.coinType) === 6) {
|
||||
const { data } = fromBech32(wallet.cosmosAddress)
|
||||
available.push({
|
||||
chainName: chain.chainName,
|
||||
logo: chain.logo,
|
||||
address: toBech32(chain.bech32Prefix, data),
|
||||
coinType: chain.coinType,
|
||||
endpoint: chain.endpoints.rest?.at(0)?.address
|
||||
})
|
||||
}
|
||||
export function scanCompatibleAccounts(keys: LocalKey[]) {
|
||||
const dashboard = useDashboard()
|
||||
const available = [] as AccountEntry[]
|
||||
keys.forEach(wallet => {
|
||||
Object.values(dashboard.chains).forEach(chain => {
|
||||
const { data } = fromBech32(wallet.cosmosAddress)
|
||||
available.push({
|
||||
chainName: chain.chainName,
|
||||
logo: chain.logo,
|
||||
address: toBech32(chain.bech32Prefix, data),
|
||||
coinType: chain.coinType,
|
||||
compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
|
||||
endpoint: chain.endpoints.rest?.at(0)?.address
|
||||
})
|
||||
})
|
||||
return available
|
||||
}
|
||||
})
|
||||
return available
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from '@iconify/vue';
|
||||
import {
|
||||
useDashboard,
|
||||
LoadingStatus,
|
||||
@ -6,14 +7,11 @@ import {
|
||||
} from '@/stores/useDashboard';
|
||||
import ChainSummary from '@/components/ChainSummary.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useBlockchain } from '@/stores';
|
||||
|
||||
const dashboard = useDashboard();
|
||||
|
||||
dashboard.$subscribe((mutation, state) => {
|
||||
localStorage.setItem('favorite', JSON.stringify(state.favorite));
|
||||
// TODO: cause endless loop
|
||||
// dashboard.loadingPrices()
|
||||
});
|
||||
const keywords = ref('');
|
||||
const chains = computed(() => {
|
||||
@ -28,7 +26,7 @@ const chains = computed(() => {
|
||||
</script>
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="flex items-center justify-center mb-6 mt-10">
|
||||
<div class="flex items-center justify-center mb-6 mt-14">
|
||||
<div class="w-8 md:!w-16 rounded-full mr-3">
|
||||
<img src="/logo.svg" />
|
||||
</div>
|
||||
@ -52,17 +50,11 @@ const chains = computed(() => {
|
||||
<progress class="progress progress-info w-80 h-1"></progress>
|
||||
</div>
|
||||
|
||||
<VTextField
|
||||
v-model="keywords"
|
||||
variant="underlined"
|
||||
:placeholder="$t('index.search_placeholder')"
|
||||
style="max-width: 300px"
|
||||
app
|
||||
>
|
||||
<template #append-inner>
|
||||
{{ chains.length }}/{{ dashboard.length }}
|
||||
</template>
|
||||
</VTextField>
|
||||
<div class="flex items-center rounded-full bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
|
||||
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3"/>
|
||||
<input :placeholder="$t('index.search_placeholder')" class="px-4 h-10 bg-transparent flex-1 outline-none text-base" v-model="keywords" />
|
||||
<div class="px-4 text-base">{{ chains.length }}/{{ dashboard.length }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-2 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
|
||||
|
@ -7,101 +7,111 @@ import { hashTx } from '@/libs';
|
||||
import { fromBase64 } from '@cosmjs/encoding';
|
||||
|
||||
export const useBaseStore = defineStore('baseStore', {
|
||||
state: () => {
|
||||
return {
|
||||
earlest: {} as Block,
|
||||
latest: {} as Block,
|
||||
recents: [] as Block[],
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
blocktime(): number {
|
||||
if (this.earlest && this.latest) {
|
||||
if (
|
||||
this.latest.block?.header?.height !==
|
||||
this.earlest.block?.header?.height
|
||||
) {
|
||||
const diff = dayjs(this.latest.block?.header?.time).diff(
|
||||
this.earlest.block?.header?.time
|
||||
);
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return 6000;
|
||||
state: () => {
|
||||
return {
|
||||
earlest: {} as Block,
|
||||
latest: {} as Block,
|
||||
recents: [] as Block[],
|
||||
theme: (window.localStorage.getItem('theme') || 'dark') as
|
||||
| 'light'
|
||||
| 'dark',
|
||||
};
|
||||
},
|
||||
blockchain() {
|
||||
return useBlockchain();
|
||||
},
|
||||
currentChainId(): string {
|
||||
return this.latest.block?.header.chain_id || ""
|
||||
},
|
||||
txsInRecents() {
|
||||
const txs = [] as { height: string; hash: string; tx: DecodedTxRaw }[];
|
||||
this.recents.forEach((b) =>
|
||||
b.block?.data?.txs.forEach((tx: string) => {
|
||||
if (tx) {
|
||||
const raw = fromBase64(tx);
|
||||
try {
|
||||
txs.push({
|
||||
height: b.block.header.height,
|
||||
hash: hashTx(raw),
|
||||
tx: decodeTxRaw(raw),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
getters: {
|
||||
blocktime(): number {
|
||||
if (this.earlest && this.latest) {
|
||||
if (
|
||||
this.latest.block?.header?.height !==
|
||||
this.earlest.block?.header?.height
|
||||
) {
|
||||
const diff = dayjs(this.latest.block?.header?.time).diff(
|
||||
this.earlest.block?.header?.time
|
||||
);
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
return txs;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async initial() {
|
||||
this.fetchLatest();
|
||||
},
|
||||
async clearRecentBlocks() {
|
||||
this.recents = [];
|
||||
},
|
||||
async fetchLatest() {
|
||||
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
||||
if (
|
||||
!this.earlest ||
|
||||
this.earlest?.block?.header?.chain_id !=
|
||||
this.latest?.block?.header?.chain_id
|
||||
) {
|
||||
//reset earlest and recents
|
||||
this.earlest = this.latest;
|
||||
this.recents = [];
|
||||
}
|
||||
//check if the block exists in recents
|
||||
if (
|
||||
this.recents.findIndex(
|
||||
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
|
||||
) === -1
|
||||
) {
|
||||
if (this.recents.length >= 50) {
|
||||
this.recents.shift();
|
||||
}
|
||||
this.recents.push(this.latest);
|
||||
}
|
||||
return this.latest;
|
||||
return 6000;
|
||||
},
|
||||
blockchain() {
|
||||
return useBlockchain();
|
||||
},
|
||||
currentChainId(): string {
|
||||
return this.latest.block?.header.chain_id || '';
|
||||
},
|
||||
txsInRecents() {
|
||||
const txs = [] as {
|
||||
height: string;
|
||||
hash: string;
|
||||
tx: DecodedTxRaw;
|
||||
}[];
|
||||
this.recents.forEach((b) =>
|
||||
b.block?.data?.txs.forEach((tx: string) => {
|
||||
if (tx) {
|
||||
const raw = fromBase64(tx);
|
||||
try {
|
||||
txs.push({
|
||||
height: b.block.header.height,
|
||||
hash: hashTx(raw),
|
||||
tx: decodeTxRaw(raw),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
return txs;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async initial() {
|
||||
this.fetchLatest();
|
||||
},
|
||||
async clearRecentBlocks() {
|
||||
this.recents = [];
|
||||
},
|
||||
async fetchLatest() {
|
||||
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
||||
if (
|
||||
!this.earlest ||
|
||||
this.earlest?.block?.header?.chain_id !=
|
||||
this.latest?.block?.header?.chain_id
|
||||
) {
|
||||
//reset earlest and recents
|
||||
this.earlest = this.latest;
|
||||
this.recents = [];
|
||||
}
|
||||
//check if the block exists in recents
|
||||
if (
|
||||
this.recents.findIndex(
|
||||
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
|
||||
) === -1
|
||||
) {
|
||||
if (this.recents.length >= 50) {
|
||||
this.recents.shift();
|
||||
}
|
||||
this.recents.push(this.latest);
|
||||
}
|
||||
return this.latest;
|
||||
},
|
||||
|
||||
async fetchValidatorByHeight(height?: number, offset = 0) {
|
||||
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset);
|
||||
async fetchValidatorByHeight(height?: number, offset = 0) {
|
||||
return this.blockchain.rpc.getBaseValidatorsetAt(
|
||||
String(height),
|
||||
offset
|
||||
);
|
||||
},
|
||||
async fetchLatestValidators(offset = 0) {
|
||||
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
|
||||
},
|
||||
async fetchBlock(height?: number | string) {
|
||||
return this.blockchain.rpc.getBaseBlockAt(String(height));
|
||||
},
|
||||
async fetchAbciInfo() {
|
||||
return this.blockchain.rpc.getBaseNodeInfo();
|
||||
},
|
||||
// async fetchNodeInfo() {
|
||||
// return this.blockchain.rpc.no()
|
||||
// }
|
||||
},
|
||||
async fetchLatestValidators(offset = 0) {
|
||||
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
|
||||
},
|
||||
async fetchBlock(height?: number | string) {
|
||||
return this.blockchain.rpc.getBaseBlockAt(String(height));
|
||||
},
|
||||
async fetchAbciInfo() {
|
||||
return this.blockchain.rpc.getBaseNodeInfo();
|
||||
},
|
||||
// async fetchNodeInfo() {
|
||||
// return this.blockchain.rpc.no()
|
||||
// }
|
||||
},
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import type {
|
||||
WalletConnected,
|
||||
} from '@/types';
|
||||
import { useStakingStore } from './useStakingStore';
|
||||
import router from '@/router'
|
||||
|
||||
export const useWalletStore = defineStore('walletStore', {
|
||||
state: () => {
|
||||
@ -17,7 +18,7 @@ export const useWalletStore = defineStore('walletStore', {
|
||||
delegations: [] as Delegation[],
|
||||
unbonding: [] as UnbondingResponses[],
|
||||
rewards: {} as DelegatorRewards,
|
||||
walletIsConnected: {} as WalletConnected | null
|
||||
walletIsConnected: {} as WalletConnected
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
@ -133,6 +134,10 @@ export const useWalletStore = defineStore('walletStore', {
|
||||
this.walletIsConnected = value || {}
|
||||
// JSON.parse(localStorage.getItem(key) || '{}');
|
||||
return this.walletIsConnected
|
||||
},
|
||||
suggestChain() {
|
||||
// const router = useRouter()
|
||||
router.push({path: '/wallet/keplr'})
|
||||
}
|
||||
},
|
||||
});
|
@ -33,8 +33,4 @@ html[data-theme='dark'] {
|
||||
.table th:first-child {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply rounded;
|
||||
}
|
||||
}
|
@ -11,14 +11,7 @@ module.exports = {
|
||||
main: 'var(--text-main)',
|
||||
secondary: 'var(--text-secondary)',
|
||||
active: 'var(--bg-active)',
|
||||
},
|
||||
borderRadius: {
|
||||
none: '0',
|
||||
xs: '.125rem',
|
||||
sm: '.25rem',
|
||||
DEFAULT: '.5rem',
|
||||
lg: '.75rem',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [require('daisyui')],
|
||||
|
@ -5476,10 +5476,10 @@ pify@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
|
||||
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
|
||||
|
||||
ping-widget@^0.0.26:
|
||||
version "0.0.26"
|
||||
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.26.tgz#25089b57c0ff8e22e9f67435d23d09d33399b100"
|
||||
integrity sha512-XGwNCx8svozTTMPqCkj6YWYk87eMftLBwsrapRjDLNaJA5NL2ouUIkaEUwV5Llt287WGW+FR+8PiwvNkaYgTPQ==
|
||||
ping-widget@^0.0.30:
|
||||
version "0.0.30"
|
||||
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.30.tgz#f417cff47fb8a95e443e953bc5eb3c912801f605"
|
||||
integrity sha512-bKa47dHNqUw/TaRBbjxQkb5+yXMwEq0+EiIs3b9Q5/0HlFcs19rdzH/RDQj4S3tpClOX7N5hTpAFikOInHq20g==
|
||||
dependencies:
|
||||
"@cosmjs/amino" "^0.30.1"
|
||||
"@cosmjs/cosmwasm-stargate" "^0.30.1"
|
||||
|
Loading…
Reference in New Issue
Block a user