Merge pull request #15 from ping-pub/master

merge
This commit is contained in:
Alisa | Side.one 2023-05-29 13:54:46 +08:00 committed by GitHub
commit fdf5e07db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1450 additions and 1092 deletions

View File

@ -1,8 +1,8 @@
{ {
"chain_name": "desmos", "chain_name": "desmos",
"coingecko": "desmos", "coingecko": "desmos",
"api": ["https://api.mainnet.desmos.network"], "api": ["https://api.mainnet.desmos.network", "https://desmos-api.lavenderfive.com"],
"rpc": ["https://rpc.mainnet.desmos.network:443"], "rpc": ["https://rpc.mainnet.desmos.network:443", "https://desmos-rpc.lavenderfive.com:443"],
"sdk_version": "0.45.8", "sdk_version": "0.45.8",
"coin_type": "118", "coin_type": "118",
"min_tx_fee": "3000", "min_tx_fee": "3000",

View File

@ -1,8 +1,8 @@
{ {
"chain_name": "lum-network", "chain_name": "lum-network",
"coingecko": "lum-network", "coingecko": "lum-network",
"api": ["https://api-lum.degeno.de", "https://node0.mainnet.lum.network/rest"], "api": ["https://node0.mainnet.lum.network/rest", "https://lumnetwork-api.lavenderfive.com"],
"rpc": ["https://rpc-lum.degeno.de:443", "https://node0.mainnet.lum.network:443/rpc"], "rpc": ["https://node0.mainnet.lum.network:443/rpc", "https://lumnetwork-rpc.lavenderfive.com:443"],
"snapshot_provider": "", "snapshot_provider": "",
"sdk_version": "0.44.5", "sdk_version": "0.44.5",
"coin_type": "880", "coin_type": "880",

View File

@ -2,8 +2,8 @@
{ {
"chain_name": "shentu", "chain_name": "shentu",
"coingecko": "certik", "coingecko": "certik",
"api": ["https://certik-api.polkachu.com", "https://chainfull.noopsbycertik.com"], "api": ["https://shentu-api.polkachu.com", "https://chainfull.noopsbycertik.com"],
"rpc": ["https://certik-rpc.polkachu.com:443"], "rpc": ["https://shentu-rpc.polkachu.com:443"],
"snapshot_provider": "", "snapshot_provider": "",
"sdk_version": "0.45.9", "sdk_version": "0.45.9",
"coin_type": "118", "coin_type": "118",

19
chains/mainnet/terp.json Normal file
View 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"
}]
}

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -40,7 +40,7 @@ function gotoPage(pageNum: number) {
<div v-if="total && limit" class="btn-group"> <div v-if="total && limit" class="btn-group">
<button v-for="{ page, color } in pages" :key="page" <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="{ 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)"> }" @click="gotoPage(page)">
{{ page }} {{ page }}
</button> </button>

View File

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

View File

@ -1,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>

View File

@ -1,17 +1,26 @@
<script lang="ts" setup> <script lang="ts" setup>
import ApexCharts from 'vue3-apexcharts'; import ApexCharts from 'vue3-apexcharts';
import { computed } from 'vue'; import { computed } from 'vue';
import { useBaseStore } from '@/stores';
import { getDonutChartConfig } from './apexChartConfig'; import { getDonutChartConfig } from './apexChartConfig';
const props = defineProps(['series', 'labels']); const props = defineProps(['series', 'labels']);
const expenseRationChartConfig = computed(() => const baseStore = useBaseStore();
getDonutChartConfig(window.localStorage.getItem('theme') || 'dark', props.labels)
); const expenseRationChartConfig = computed(() => {
const theme = baseStore.theme;
getDonutChartConfig(theme, props.labels);
});
</script> </script>
<template> <template>
<ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" /> <ApexCharts
type="donut"
height="410"
:options="expenseRationChartConfig"
:series="series"
/>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -3,11 +3,14 @@ import ApexCharts from 'vue3-apexcharts';
import { getMarketPriceChartConfig } from './apexChartConfig'; import { getMarketPriceChartConfig } from './apexChartConfig';
import { useIndexModule } from '@/modules/[chain]/indexStore'; import { useIndexModule } from '@/modules/[chain]/indexStore';
import { computed, ref } from '@vue/reactivity'; import { computed, ref } from '@vue/reactivity';
import { useBaseStore } from '@/stores';
const store = useIndexModule(); const store = useIndexModule();
const baseStore = useBaseStore();
const chartConfig = computed(() => { const chartConfig = computed(() => {
const theme = baseStore.theme;
const labels = store.marketData.prices.map((item: any) => item[0]); const labels = store.marketData.prices.map((item: any) => item[0]);
return getMarketPriceChartConfig(window.localStorage.getItem('theme') || 'dark', labels); return getMarketPriceChartConfig(theme, labels);
}); });
const kind = ref('price'); const kind = ref('price');
const series = computed(() => { const series = computed(() => {
@ -17,7 +20,9 @@ const series = computed(() => {
data: data:
kind.value === 'price' kind.value === 'price'
? store.marketData.prices.map((item: any) => item[1]) ? store.marketData.prices.map((item: any) => item[1])
: store.marketData.total_volumes.map((item: any) => item[1]), : store.marketData.total_volumes.map(
(item: any) => item[1]
),
}, },
]; ];
}); });
@ -29,14 +34,25 @@ function changeChart(type: string) {
<template> <template>
<div class="tabs tabs-boxed bg-transparent justify-end"> <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' }" <a
@click="changeChart('price')"> class="tab text-xs mr-2 text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'price' }"
@click="changeChart('price')"
>
Price Price
</a> </a>
<a class="tab text-xs text-gray-400 uppercase" :class="{ 'tab-active': kind === 'volume' }" <a
@click="changeChart('volume')"> class="tab text-xs text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'volume' }"
@click="changeChart('volume')"
>
Volume Volume
</a> </a>
</div> </div>
<ApexCharts type="area" height="230" :options="chartConfig" :series="series" /> <ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
</template> </template>

View File

@ -18,7 +18,7 @@ const header = computed(() => {
}); });
</script> </script>
<template> <template>
<div class="overflow-x-auto p-4"> <div class="overflow-auto h-[500px] p-4">
<div <div
v-if="header.length > 0" v-if="header.length > 0"
class="table table-compact w-full" class="table table-compact w-full"

View File

@ -4,11 +4,16 @@ import { select } from './index';
const props = defineProps(['value']); const props = defineProps(['value']);
</script> </script>
<template> <template>
<div class="overflow-x-auto"> <div class="overflow-auto">
<table class="table table-compact w-full text-sm"> <table class="table table-compact w-full text-sm">
<tbody> <tbody>
<tr v-for="(v, k) of value"> <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> <td>
<div <div
class="overflow-hidden w-auto whitespace-normal" class="overflow-hidden w-auto whitespace-normal"

View File

@ -85,6 +85,8 @@ function changeEndpoint(item: Endpoint) {
Height: {{ baseStore.latest.block?.header.height }} Height: {{ baseStore.latest.block?.header.height }}
</div> </div>
</div> </div>
<!-- bottom-->
<div class="px-4 py-2">&nbsp;</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -39,56 +39,108 @@ const showDiscord = window.location.host.search('ping.pub') > -1;
<template> <template>
<div class="bg-gray-100 dark:bg-[#171d30]"> <div class="bg-gray-100 dark:bg-[#171d30]">
<!-- sidebar --> <!-- 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" <div
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"> 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"> <div class="flex items-center pl-4 py-4 mb-1">
<img class="w-10 h-10" src="../../assets/logo.svg" /> <img class="w-10 h-10" src="../../assets/logo.svg" />
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white"> <h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
Ping.pub Ping.pub
</h1> </h1>
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false"> <div
class="pr-4 cursor-pointer xl:!hidden"
@click="sidebarShow = false"
>
<Icon icon="mdi-close" class="text-3xl" /> <Icon icon="mdi-close" class="text-3xl" />
</div> </div>
</div> </div>
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2"> <div
<div v-if="item?.title && item?.children?.length" :tabindex="index" class="collapse" :class="{ 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-arrow': item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen, 'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen, 'collapse-close': index === 0 && !sidebarOpen,
}"> }"
<input type="checkbox" class="cursor-pointer" @click="changeOpen(index)" /> >
<input
type="checkbox"
class="cursor-pointer !h-10 block"
@click="changeOpen(index)"
/>
<div <div
class="collapse-title px-4 flex items-center h-12 cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"> 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="{ >
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite', 'text-yellow-500': item?.title === 'Favorite',
'text-blue-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"> <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 }} {{ item?.title }}
</div> </div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass"> <div
v-if="item?.badgeContent"
class="mr-6 badge badge-sm text-white"
:class="item?.badgeClass"
>
{{ item?.badgeContent }} {{ item?.badgeContent }}
</div> </div>
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<div class="menu bg-base-100 w-full"> <div class="menu bg-base-100 w-full">
<RouterLink v-for="(el, key) of item?.children" @click="sidebarShow = false" :key="key" <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" class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:to="el?.to" :class="{ :to="el?.to"
:class="{
'!bg-primary': '!bg-primary':
$route.path === el?.to?.path && item?.title !== 'Favorite', $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 && <Icon
item?.title !== 'Favorite', v-if="!el?.icon?.image"
}" /> icon="mdi:chevron-right"
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4" /> class="mr-2 ml-3"
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{ :class="{
'text-white': 'text-white':
$route.path === el?.to?.path && $route.path === el?.to?.path &&
item?.title !== 'Favorite', 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) }} {{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
</div> </div>
</RouterLink> </RouterLink>
@ -96,63 +148,116 @@ const showDiscord = window.location.host.search('ping.pub') > -1;
</div> </div>
</div> </div>
<RouterLink :to="item?.to" v-if="item?.title && !item?.children?.length && item?.to" <RouterLink
:to="item?.to"
v-if="item?.title && !item?.children?.length && item?.to"
@click="sidebarShow = false" @click="sidebarShow = false"
class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"> 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="{ >
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite', 'text-yellow-500': item?.title === 'Favorite',
'text-blue-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"> <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 }} {{ item?.title }}
</div> </div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass"> <div
v-if="item?.badgeContent"
class="badge badge-sm text-white"
:class="item?.badgeClass"
>
{{ item?.badgeContent }} {{ item?.badgeContent }}
</div> </div>
</RouterLink> </RouterLink>
<div v-if="item?.heading" class="px-4 text-sm pt-3 text-gray-400 pb-2 uppercase"> <div
v-if="item?.heading"
class="px-4 text-sm text-gray-400 pb-2 uppercase"
>
{{ item?.heading }} {{ item?.heading }}
</div> </div>
</div> </div>
<div class="px-2"> <div class="px-2">
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase"> <div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Sponsors Sponsors
</div> </div>
<a href="https://osmosis.zone" <a
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> href="https://osmosis.zone"
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" /> target="_blank"
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> 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 Osmosis
</div> </div>
</a> </a>
<a href="https://becole.com" <a
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> href="https://becole.com"
<img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3" /> target="_blank"
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> 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 Becole
</div> </div>
</a> </a>
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">Links</div> <div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">Links</div>
<a href="https://twitter.com/ping_pub" <a
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> 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" /> <Icon icon="mdi:twitter" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Twitter Twitter
</div> </div>
</a> </a>
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW" <a
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> 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" /> <Icon icon="mdi:discord" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Discord Discord
</div> </div>
</a> </a>
<a href="https://github.com/ping-pub/explorer/discussions" <a
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> 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" /> <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"> <div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
FAQ FAQ
</div> </div>
</a> </a>
@ -160,8 +265,13 @@ const showDiscord = window.location.host.search('ping.pub') > -1;
</div> </div>
<div class="xl:!ml-64 px-5 pt-4"> <div class="xl:!ml-64 px-5 pt-4">
<!-- header --> <!-- header -->
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow"> <div
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true"> 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" /> <Icon icon="mdi-menu" />
</div> </div>

View File

@ -34,11 +34,16 @@ const handleLangChange = (lang: string) => {
<div <div
class="dropdown" class="dropdown"
:class=" :class="
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end' currentLang === 'ar'
? 'dropdown-right'
: 'dropdown-bottom dropdown-end'
" "
> >
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1"> <label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon icon="mdi-translate" class="text-2xl" /> <Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label> </label>
<ul <ul
tabindex="0" tabindex="0"

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores'; import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { ref, computed } from 'vue'; import { ref, computed, } from 'vue';
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
@ -37,7 +38,7 @@ const tipMsg = computed(() => {
<div class="dropdown dropdown-hover dropdown-end"> <div class="dropdown dropdown-hover dropdown-end">
<label <label
tabindex="0" 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" /> <Icon icon="mdi:wallet" />
<span class="ml-1 hidden md:block"> <span class="ml-1 hidden md:block">
@ -51,7 +52,7 @@ const tipMsg = computed(() => {
<label <label
v-if="!walletStore?.currentAddress" v-if="!walletStore?.currentAddress"
for="PingConnectWallet" for="PingConnectWallet"
class="btn btn-sm" class="btn btn-sm btn-primary"
><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block" ><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block"
>Connect Wallet</span >Connect Wallet</span
></label ></label
@ -108,6 +109,7 @@ const tipMsg = computed(() => {
:chain-id="baseStore.currentChainId" :chain-id="baseStore.currentChainId"
:hd-path="chainStore.defaultHDPath" :hd-path="chainStore.defaultHDPath"
@connect="walletStateChange" @connect="walletStateChange"
@keplr-config="walletStore.suggestChain()"
/> />
</Teleport> </Teleport>
</template> </template>

View File

@ -1,23 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue'; 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?: 'dark' | 'light') => {
let value: 'dark' | 'light' = 'dark';
const changeMode = (val?: string) => { const currentValue: 'dark' | 'light' = val || theme.value;
let value = 'dark';
const currentValue = val || theme.value;
if (currentValue === 'dark') { if (currentValue === 'dark') {
value = 'light'; value = 'light';
} }
if (
currentValue === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
value = 'dark';
}
if (value === 'light') { if (value === 'light') {
document.documentElement.classList.add('light'); document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark');
@ -27,10 +28,9 @@ const changeMode = (val?: string) => {
} }
document.documentElement.setAttribute('data-theme', value); document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value); window.localStorage.setItem('theme', value);
theme.value = value; baseStore.theme = value;
}; };
onMounted(() => { onMounted(() => {
changeMode(theme.value === 'light' ? 'dark' : 'light'); changeMode(theme.value === 'light' ? 'dark' : 'light');
}); });
@ -38,8 +38,11 @@ onMounted(() => {
<template> <template>
<div class="tooltip tooltip-bottom delay-1000"> <div class="tooltip tooltip-bottom delay-1000">
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()"> <button
<Icon :icon="themeMap?.[theme]" class="text-2xl" /> 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> </button>
</div> </div>
</template> </template>

View File

@ -13,20 +13,20 @@ import { PageRequest } from '@/types';
const props = defineProps(['code_id', 'chain']); const props = defineProps(['code_id', 'chain']);
const pageRequest = ref(new PageRequest()) const pageRequest = ref(new PageRequest());
const response = ref({} as PaginabledContracts); const response = ref({} as PaginabledContracts);
const wasmStore = useWasmStore(); const wasmStore = useWasmStore();
function loadContract(pageNum: number) { function loadContract(pageNum: number) {
const pr = new PageRequest() const pr = new PageRequest();
pr.setPage(pageNum) pr.setPage(pageNum);
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => { wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x; response.value = x;
}); });
} }
loadContract(1) loadContract(1);
const dialog = useTxDialog() const dialog = useTxDialog();
const format = useFormatter(); const format = useFormatter();
const infoDialog = ref(false); const infoDialog = ref(false);
const info = ref({} as ContractInfo); const info = ref({} as ContractInfo);
@ -39,13 +39,15 @@ function showInfo(address: string) {
}); });
} }
function showState(address: string) { function showState(address: string) {
selected.value = address selected.value = address;
pageload(1) pageload(1);
} }
function pageload(p: number) { function pageload(p: number) {
pageRequest.value.setPage(p) pageRequest.value.setPage(p);
wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => { wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x; state.value = x;
}); });
} }
@ -115,22 +117,39 @@ const result = ref('');
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(v, index) in response.contracts" :key="index" class="hover"> <tr
v-for="(v, index) in response.contracts"
:key="index"
class="hover"
>
<td>{{ v }}</td> <td>{{ v }}</td>
<td> <td>
<label @click="showInfo(v)" for="modal-contract-detail" <label
class="btn btn-primary btn-xs text-xs mr-2">contract</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" <label
@click="showState(v)"> class="btn btn-primary btn-xs text-xs mr-2"
for="modal-contract-states"
@click="showState(v)"
>
States States
</label> </label>
<label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2" <label
@click="showQuery(v)"> for="modal-contract-query"
class="btn btn-primary btn-xs text-xs mr-2"
@click="showQuery(v)"
>
Query Query
</label> </label>
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs" <label
@click="dialog.open('wasm_execute_contract', { contract: v })"> for="wasm_execute_contract"
class="btn btn-primary btn-xs text-xs"
@click="dialog.open('wasm_execute_contract', { contract: v })"
>
Execute Execute
</label> </label>
</td> </td>
@ -138,10 +157,21 @@ const result = ref('');
</tbody> </tbody>
</table> </table>
<div class="flex justify-between"> <div class="flex justify-between">
<PaginationBar :limit="50" :total="response.pagination?.total" :callback="loadContract" /> <PaginationBar
<label for="wasm_instantiate_contract" class="btn btn-primary my-5" :limit="50"
@click="dialog.open('wasm_instantiate_contract', { codeId: props.code_id })">Instantiate :total="response.pagination?.total"
Contract</label> :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> </div>
</div> </div>
@ -152,8 +182,12 @@ const result = ref('');
<div> <div>
<div class="flex items-center justify-between px-3 pt-2"> <div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">Contract Detail</div> <div class="text-lg">Contract Detail</div>
<label @click="infoDialog = false" for="modal-contract-detail" <label
class="btn btn-sm btn-circle"></label> @click="infoDialog = false"
for="modal-contract-detail"
class="btn btn-sm btn-circle"
></label
>
</div> </div>
<div> <div>
<DynamicComponent :value="info" /> <DynamicComponent :value="info" />
@ -168,8 +202,12 @@ const result = ref('');
<div> <div>
<div class="flex items-center justify-between px-3 pt-2 mb-4"> <div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">Contract States</div> <div class="text-lg">Contract States</div>
<label @click="infoDialog = false" for="modal-contract-states" <label
class="btn btn-sm btn-circle"></label> @click="infoDialog = false"
for="modal-contract-states"
class="btn btn-sm btn-circle"
></label
>
</div> </div>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-compact w-full text-sm"> <table class="table table-compact w-full text-sm">
@ -177,12 +215,19 @@ const result = ref('');
<td class="" :data-tip="format.hexToString(v.key)"> <td class="" :data-tip="format.hexToString(v.key)">
<span class="font-bold">{{ format.hexToString(v.key) }}</span> <span class="font-bold">{{ format.hexToString(v.key) }}</span>
</td> </td>
<td class="text-left w-3/4" :title="format.base64ToString(v.value)"> <td
class="text-left w-3/4"
:title="format.base64ToString(v.value)"
>
{{ format.base64ToString(v.value) }} {{ format.base64ToString(v.value) }}
</td> </td>
</tr> </tr>
</table> </table>
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageload" /> <PaginationBar
:limit="pageRequest.limit"
:total="state.pagination?.total"
:callback="pageload"
/>
</div> </div>
</div> </div>
</label> </label>
@ -194,17 +239,33 @@ const result = ref('');
<div> <div>
<div class="flex items-center justify-between px-3 pt-2 mb-4"> <div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">Query Contract</div> <div class="text-lg font-semibold">Query Contract</div>
<label @click="infoDialog = false" for="modal-contract-query" <label
class="btn btn-sm btn-circle"></label> @click="infoDialog = false"
for="modal-contract-query"
class="btn btn-sm btn-circle"
></label
>
</div> </div>
<div class="px-3"> <div class="px-3">
<div> <div>
<div class="grid grid-cols-2 gap-4 mb-4"> <div class="grid grid-cols-2 gap-4 mb-4">
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent" <div
:key="index" :class="{ 'pt-2': index === 0 }"> class="form-control border rounded px-4"
<label class="label cursor-pointer justify-start" @click="selectedRadio = item?.value"> v-for="(item, index) of radioContent"
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4" :key="index"
:checked="item?.value === selectedRadio" style="border: 1px solid #d2d6dc" /> :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>
<div class="text-base font-semibold"> <div class="text-base font-semibold">
{{ item?.title }} {{ item?.title }}
@ -219,7 +280,10 @@ const result = ref('');
<VTextarea v-model="result" label="Result" /> <VTextarea v-model="result" label="Result" />
</div> </div>
<div class="mt-4 mb-4"> <div class="mt-4 mb-4">
<button class="btn !btn-yes !border-yes px-4 text-white" @click="queryContract()"> <button
class="btn !btn-yes !border-yes px-4 text-white"
@click="queryContract()"
>
Query Contract Query Contract
</button> </button>
</div> </div>
@ -227,4 +291,5 @@ const result = ref('');
</div> </div>
</label> </label>
</label> </label>
</div></template> </div>
</template>

View File

@ -321,19 +321,21 @@ const color = computed(() => {
</Teleport> </Teleport>
</div> </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"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Application Versions Application Versions
</div> </div>
<!-- Application Version --> <!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" /> <ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
<div class="h-4"></div>
</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"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Node Information Node Information
</div> </div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" /> <ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<div class="h-4"></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -45,7 +45,7 @@ onMounted(() => {
</div> </div>
<!-- Node Information --> <!-- 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> <div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" /> <ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
</div> </div>

View File

@ -106,7 +106,9 @@ const loadAvatars = () => {
if (identity && !avatars.value[identity]) { if (identity && !avatars.value[identity]) {
staking.keybase(identity).then((d) => { staking.keybase(identity).then((d) => {
if (Array.isArray(d.them) && d.them.length > 0) { if (Array.isArray(d.them) && d.them.length > 0) {
const uri = String(d.them[0]?.pictures?.primary?.url).replace( const uri = String(
d.them[0]?.pictures?.primary?.url
).replace(
'https://s3.amazonaws.com/keybase_processed_uploads/', 'https://s3.amazonaws.com/keybase_processed_uploads/',
'' ''
); );
@ -154,7 +156,6 @@ const rank = function (position: number) {
fetchChange(); fetchChange();
loadAvatars(); loadAvatars();
</script> </script>
<template> <template>
<div> <div>
@ -184,7 +185,12 @@ loadAvatars();
<table class="table staking-table w-full"> <table class="table staking-table w-full">
<thead> <thead>
<tr> <tr>
<th scope="col" style="width: 3rem; position: relative">#</th> <th
scope="col"
style="width: 3rem; position: relative"
>
#
</th>
<th scope="col">VALIDATOR</th> <th scope="col">VALIDATOR</th>
<th scope="col" class="text-right">VOTING POWER</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">24h CHANGES</th>
@ -226,7 +232,11 @@ loadAvatars();
<div class="w-8 h-8 rounded-full"> <div class="w-8 h-8 rounded-full">
<img <img
v-if="v.description?.identity" v-if="v.description?.identity"
v-lazy="logo(v.description?.identity)" v-lazy="
logo(
v.description?.identity
)
"
class="object-contain" class="object-contain"
/> />
<Icon <Icon
@ -242,7 +252,10 @@ loadAvatars();
<RouterLink <RouterLink
:to="{ :to="{
name: 'chain-staking-validator', name: 'chain-staking-validator',
params: { validator: v.operator_address }, params: {
validator:
v.operator_address,
},
}" }"
class="font-weight-medium user-list-name" class="font-weight-medium user-list-name"
> >
@ -250,7 +263,9 @@ loadAvatars();
</RouterLink> </RouterLink>
</h6> </h6>
<span class="text-xs">{{ <span class="text-xs">{{
v.description?.website || v.description?.identity || '-' v.description?.website ||
v.description?.identity ||
'-'
}}</span> }}</span>
</div> </div>
</div> </div>
@ -263,8 +278,11 @@ loadAvatars();
{{ {{
format.formatToken( format.formatToken(
{ {
amount: parseInt(v.tokens).toString(), amount: parseInt(
denom: staking.params.bond_denom, v.tokens
).toString(),
denom: staking.params
.bond_denom,
}, },
true, true,
'0,0' '0,0'
@ -296,20 +314,24 @@ loadAvatars();
</td> </td>
<!-- 👉 Action --> <!-- 👉 Action -->
<td class="text-center"> <td class="text-center">
<div v-if="v.jailed" class="badge badge-error gap-2 text-white"> <div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
Jailed Jailed
</div> </div>
<label <label
v-else v-else
for="delegate" for="delegate"
class="btn btn-xs rounded bg-primary capitalize border-none" class="btn btn-xs btn-primary rounded-sm capitalize"
@click=" @click="
dialog.open('delegate', { dialog.open('delegate', {
validator_address: v.operator_address, validator_address:
v.operator_address,
}) })
" "
>Delegate</label> >Delegate</label
>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -321,7 +343,9 @@ loadAvatars();
<div <div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2" 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> <span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
Top 33% Top 33%
</div> </div>
<div <div

View File

@ -128,7 +128,7 @@ function changeTab(v: string) {
<input type="text" v-model="keyword" placeholder="Keywords to filter validators" <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" /> class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
</div> </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 v-for="({ v, signing, hex }, i) in list" :key="i">
<div class="flex items-center justify-between py-0"> <div class="flex items-center justify-between py-0">
<label class="truncate text-sm"> <label class="truncate text-sm">

View File

@ -2,15 +2,17 @@
import { CosmosRestClient } from '@/libs/client'; import { CosmosRestClient } from '@/libs/client';
import { useDashboard, useFormatter } from '@/stores'; import { useDashboard, useFormatter } from '@/stores';
import type { Coin, Delegation } from '@/types'; 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 { Icon } from '@iconify/vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { ref } 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 dashboard = useDashboard()
const format = useFormatter() const format = useFormatter()
const editable = ref(false) // to edit addresses const editable = ref(false) // to edit addresses
const sourceAddress = ref('') //
const selectedSource = ref({} as LocalKey) //
function toggleEdit() { function toggleEdit() {
editable.value = !editable.value 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 balances = ref({} as Record<string, Coin[]>)
const delegations = ref({} as Record<string, Delegation[]>) const delegations = ref({} as Record<string, Delegation[]>)
scanLocalKeys().forEach(wallet => { // load balances
const { data } = fromBech32(wallet.cosmosAddress) Object.values(conf.value).forEach(imported => {
const walletKey = toBase64(data) let promise = Promise.resolve()
let imported = conf.value[walletKey] for (let i = 0; i < imported.length; i++) {
// save the default address to local storage promise = promise.then(() => new Promise((resolve) => {
if (!imported) { // continue only if the page is living
imported = [] if (imported[i].endpoint) {
dashboard.favorite.forEach(x => { loadBalances(imported[i].endpoint || "", imported[i].address).finally(() => resolve())
const chain = dashboard.chains[x] } else {
if (chain && wallet.hdPath.indexOf(chain.coinType) === 6) { resolve()
imported.push({
chainName: chain.chainName,
logo: chain.logo,
address: toBech32(chain.bech32Prefix, data),
coinType: chain.coinType,
endpoint: chain.endpoints.rest?.at(0)?.address
})
} }
}) }))
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(() => { const accounts = computed(() => {
let a = [] as AccountEntry[] let a = [] as AccountEntry[]
@ -69,6 +50,8 @@ const accounts = computed(() => {
denom = b.balance.denom denom = b.balance.denom
}) })
entry.delegation = { amount: String(amount), denom } entry.delegation = { amount: String(amount), denom }
} else {
entry.delegation = undefined
} }
entry.balances = balances.value[entry.address] entry.balances = balances.value[entry.address]
}) })
@ -81,43 +64,97 @@ const addresses = computed(() => {
return accounts.value.map(x => (x.address)) 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(() => { 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) { function removeAddress(addr: string) {
const newConf = {} as Record<string, AccountEntry[]> const newConf = {} as Record<string, AccountEntry[]>
Object.keys(conf.value).forEach(key => { 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 conf.value = newConf
localStorage.setItem("imported-addresses", JSON.stringify(conf.value)) localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
} }
async function addAddress(acc: AccountEntry) { async function addAddress(acc: AccountEntry) {
console.log('add', acc) const { data } = fromBech32(acc.address)
const {data} = fromBech32(acc.address)
const key = toBase64(data) 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) conf.value[key].push(acc)
} else { } else {
conf.value[key] = [acc] conf.value[key] = [acc]
} }
if(acc.endpoint) { // also add chain to favorite
const client = CosmosRestClient.newDefault(acc.endpoint) if (!dashboard?.favoriteMap?.[acc.chainName]) {
client.getBankBalances(acc.address).then(res => { dashboard.favoriteMap[acc.chainName] = true
balances.value[acc.address] = res.balances.filter(x => x.denom.length < 10) window.localStorage.setItem(
}) 'favoriteMap',
client.getStakingDelegations(acc.address).then(res => { JSON.stringify(dashboard.favoriteMap)
delegations.value[acc.address] = res.delegation_responses );
}) }
if (acc.endpoint) {
loadBalances(acc.endpoint, acc.address)
} }
localStorage.setItem("imported-addresses", JSON.stringify(conf.value)) 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> </script>
<template> <template>
<div> <div>
@ -140,38 +177,13 @@ async function addAddress(acc: AccountEntry) {
</div> </div>
</div> </div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"> <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>
</div> </div>
<table class="table w-full">
<table class="table table-compact w-full">
<!-- head --> <!-- head -->
<thead> <thead class="rounded-none">
<tr> <tr>
<th v-if="editable"></th> <th v-if="editable"></th>
<th>Account</th> <th>Account</th>
@ -186,11 +198,11 @@ async function addAddress(acc: AccountEntry) {
<td v-if="editable"> <td v-if="editable">
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon> <Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
</td> </td>
<td> <td class="px-4">
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`"> <RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div class="avatar"> <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" /> <img :src="acc.logo" :alt="acc.address" />
</div> </div>
</div> </div>
@ -224,7 +236,33 @@ async function addAddress(acc: AccountEntry) {
</tbody> </tbody>
<tfoot> <tfoot>
<th colspan="10"> <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> </th>
</tfoot> </tfoot>
</table> </table>
@ -232,21 +270,27 @@ async function addAddress(acc: AccountEntry) {
<!-- Put this part before </body> tag --> <!-- Put this part before </body> tag -->
<div class="modal" id="address-modal"> <div class="modal" id="address-modal">
<div class="modal-box"> <div class="modal-box">
<h3 class="font-bold text-lg">Import Accounts <h3 class="font-bold text-lg mb-2">Derive Account From Address
<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>
</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> </h3>
<p class="py-4 max-h-60 overflow-y-auto">
<table> <div>
<tr v-for="acc in availableAccount" > <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>
<div class="py-4 max-h-72 overflow-y-auto">
<table class="table table-compact">
<tr v-for="acc in availableAccount">
<td> <td>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div class="avatar"> <div class="avatar">
@ -255,22 +299,28 @@ async function addAddress(acc: AccountEntry) {
</div> </div>
</div> </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 class="text-xs opacity-50 hidden md:!block">{{ acc.address }}</div>
</div> </div>
</div> </div>
</td> </td>
<td class="text-right"> <td class="text-right">
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)"> <span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)">
<Icon icon="mdi:plus"/> <Icon icon="mdi:plus" />
</span> </span>
</td> </td>
</tr> </tr>
</table> </table>
</p> </div>
<div class="modal-action"> <div class="modal-action mt-2 mb-0">
<a href="#" class="btn">Close</a> <a href="#" class="btn btn-primary btn-sm">Close</a>
</div> </div>
</div> </div>
</div> </div>
</div></template> </div>
</template>

View File

@ -10,10 +10,15 @@ export interface AccountEntry {
endpoint?: string, endpoint?: string,
delegation?: Coin, delegation?: Coin,
balances?: Coin[], balances?: Coin[],
compatiable?: boolean,
}
export interface LocalKey {
cosmosAddress: string, hdPath: string
} }
export function scanLocalKeys() { export function scanLocalKeys() {
const connected = [] as {cosmosAddress: string, hdPath: string}[] const connected = [] as LocalKey[]
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i) const key = localStorage.key(i)
if (key?.startsWith("m/44")) { if (key?.startsWith("m/44")) {
@ -24,25 +29,23 @@ export function scanLocalKeys() {
} }
} }
return connected return connected
} }
export function scanCompatibleAccounts(keys: LocalKey[]) {
export function scanCompatibleAccounts() {
const dashboard = useDashboard() const dashboard = useDashboard()
const available = [] as AccountEntry[] const available = [] as AccountEntry[]
scanLocalKeys().forEach(wallet => { keys.forEach(wallet => {
Object.values(dashboard.chains).forEach(chain => { Object.values(dashboard.chains).forEach(chain => {
if (wallet.hdPath.indexOf(chain.coinType) === 6) {
const { data } = fromBech32(wallet.cosmosAddress) const { data } = fromBech32(wallet.cosmosAddress)
available.push({ available.push({
chainName: chain.chainName, chainName: chain.chainName,
logo: chain.logo, logo: chain.logo,
address: toBech32(chain.bech32Prefix, data), address: toBech32(chain.bech32Prefix, data),
coinType: chain.coinType, coinType: chain.coinType,
compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
endpoint: chain.endpoints.rest?.at(0)?.address endpoint: chain.endpoints.rest?.at(0)?.address
}) })
}
}) })
}) })
return available return available
} }

View File

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Icon } from '@iconify/vue';
import { import {
useDashboard, useDashboard,
LoadingStatus, LoadingStatus,
@ -6,14 +7,11 @@ import {
} from '@/stores/useDashboard'; } from '@/stores/useDashboard';
import ChainSummary from '@/components/ChainSummary.vue'; import ChainSummary from '@/components/ChainSummary.vue';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useBlockchain } from '@/stores';
const dashboard = useDashboard(); const dashboard = useDashboard();
dashboard.$subscribe((mutation, state) => { dashboard.$subscribe((mutation, state) => {
localStorage.setItem('favorite', JSON.stringify(state.favorite)); localStorage.setItem('favorite', JSON.stringify(state.favorite));
// TODO: cause endless loop
// dashboard.loadingPrices()
}); });
const keywords = ref(''); const keywords = ref('');
const chains = computed(() => { const chains = computed(() => {
@ -28,7 +26,7 @@ const chains = computed(() => {
</script> </script>
<template> <template>
<div class=""> <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"> <div class="w-8 md:!w-16 rounded-full mr-3">
<img src="/logo.svg" /> <img src="/logo.svg" />
</div> </div>
@ -52,17 +50,11 @@ const chains = computed(() => {
<progress class="progress progress-info w-80 h-1"></progress> <progress class="progress progress-info w-80 h-1"></progress>
</div> </div>
<VTextField <div class="flex items-center rounded-full bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
v-model="keywords" <Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3"/>
variant="underlined" <input :placeholder="$t('index.search_placeholder')" class="px-4 h-10 bg-transparent flex-1 outline-none text-base" v-model="keywords" />
:placeholder="$t('index.search_placeholder')" <div class="px-4 text-base">{{ chains.length }}/{{ dashboard.length }}</div>
style="max-width: 300px" </div>
app
>
<template #append-inner>
{{ chains.length }}/{{ dashboard.length }}
</template>
</VTextField>
<div <div
class="grid grid-cols-2 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5" class="grid grid-cols-2 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"

View File

@ -12,6 +12,9 @@ export const useBaseStore = defineStore('baseStore', {
earlest: {} as Block, earlest: {} as Block,
latest: {} as Block, latest: {} as Block,
recents: [] as Block[], recents: [] as Block[],
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
}; };
}, },
getters: { getters: {
@ -33,10 +36,14 @@ export const useBaseStore = defineStore('baseStore', {
return useBlockchain(); return useBlockchain();
}, },
currentChainId(): string { currentChainId(): string {
return this.latest.block?.header.chain_id || "" return this.latest.block?.header.chain_id || '';
}, },
txsInRecents() { txsInRecents() {
const txs = [] as { height: string; hash: string; tx: DecodedTxRaw }[]; const txs = [] as {
height: string;
hash: string;
tx: DecodedTxRaw;
}[];
this.recents.forEach((b) => this.recents.forEach((b) =>
b.block?.data?.txs.forEach((tx: string) => { b.block?.data?.txs.forEach((tx: string) => {
if (tx) { if (tx) {
@ -89,7 +96,10 @@ export const useBaseStore = defineStore('baseStore', {
}, },
async fetchValidatorByHeight(height?: number, offset = 0) { async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset); return this.blockchain.rpc.getBaseValidatorsetAt(
String(height),
offset
);
}, },
async fetchLatestValidators(offset = 0) { async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest(offset); return this.blockchain.rpc.getBaseValidatorsetLatest(offset);

View File

@ -9,6 +9,7 @@ import type {
WalletConnected, WalletConnected,
} from '@/types'; } from '@/types';
import { useStakingStore } from './useStakingStore'; import { useStakingStore } from './useStakingStore';
import router from '@/router'
export const useWalletStore = defineStore('walletStore', { export const useWalletStore = defineStore('walletStore', {
state: () => { state: () => {
@ -17,7 +18,7 @@ export const useWalletStore = defineStore('walletStore', {
delegations: [] as Delegation[], delegations: [] as Delegation[],
unbonding: [] as UnbondingResponses[], unbonding: [] as UnbondingResponses[],
rewards: {} as DelegatorRewards, rewards: {} as DelegatorRewards,
walletIsConnected: {} as WalletConnected | null walletIsConnected: {} as WalletConnected
}; };
}, },
getters: { getters: {
@ -133,6 +134,10 @@ export const useWalletStore = defineStore('walletStore', {
this.walletIsConnected = value || {} this.walletIsConnected = value || {}
// JSON.parse(localStorage.getItem(key) || '{}'); // JSON.parse(localStorage.getItem(key) || '{}');
return this.walletIsConnected return this.walletIsConnected
},
suggestChain() {
// const router = useRouter()
router.push({path: '/wallet/keplr'})
} }
}, },
}); });

View File

@ -34,7 +34,3 @@ html[data-theme='dark'] {
position: relative; position: relative;
z-index: 2; z-index: 2;
} }
.btn {
@apply rounded;
}

View File

@ -11,14 +11,7 @@ module.exports = {
main: 'var(--text-main)', main: 'var(--text-main)',
secondary: 'var(--text-secondary)', secondary: 'var(--text-secondary)',
active: 'var(--bg-active)', active: 'var(--bg-active)',
}, }
borderRadius: {
none: '0',
xs: '.125rem',
sm: '.25rem',
DEFAULT: '.5rem',
lg: '.75rem',
},
}, },
}, },
plugins: [require('daisyui')], plugins: [require('daisyui')],

View File

@ -5476,10 +5476,10 @@ pify@^3.0.0:
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
ping-widget@^0.0.26: ping-widget@^0.0.30:
version "0.0.26" version "0.0.30"
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.26.tgz#25089b57c0ff8e22e9f67435d23d09d33399b100" resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.30.tgz#f417cff47fb8a95e443e953bc5eb3c912801f605"
integrity sha512-XGwNCx8svozTTMPqCkj6YWYk87eMftLBwsrapRjDLNaJA5NL2ouUIkaEUwV5Llt287WGW+FR+8PiwvNkaYgTPQ== integrity sha512-bKa47dHNqUw/TaRBbjxQkb5+yXMwEq0+EiIs3b9Q5/0HlFcs19rdzH/RDQj4S3tpClOX7N5hTpAFikOInHq20g==
dependencies: dependencies:
"@cosmjs/amino" "^0.30.1" "@cosmjs/amino" "^0.30.1"
"@cosmjs/cosmwasm-stargate" "^0.30.1" "@cosmjs/cosmwasm-stargate" "^0.30.1"