forked from LaconicNetwork/cosmos-explorer
set pretttier to run on commit with husky
This commit is contained in:
131 files changed
+7727
-5437
No files matched your search
@@ -13,8 +13,8 @@ function calculateValue(value: any) {
|
||||
if (Array.isArray(value)) {
|
||||
return (value[0] && value[0].amount) || '-';
|
||||
}
|
||||
if(String(value).search(/^\d+s$/g) > -1) {
|
||||
return formatSeconds(value)
|
||||
if (String(value).search(/^\d+s$/g) > -1) {
|
||||
return formatSeconds(value);
|
||||
}
|
||||
const newValue = Number(value);
|
||||
if (`${newValue}` === 'NaN' || typeof value === 'boolean') {
|
||||
@@ -28,8 +28,8 @@ function calculateValue(value: any) {
|
||||
}
|
||||
|
||||
function formatTitle(v: string) {
|
||||
if(!v) return ""
|
||||
return v.replace(/_/g, " ")
|
||||
if (!v) return '';
|
||||
return v.replace(/_/g, ' ');
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
@@ -46,7 +46,9 @@ function formatTitle(v: string) {
|
||||
:key="index"
|
||||
class="rounded-sm bg-active px-4 py-2"
|
||||
>
|
||||
<div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div>
|
||||
<div class="text-xs mb-2 text-secondary capitalize">
|
||||
{{ formatTitle(item?.subtitle) }}
|
||||
</div>
|
||||
<div class="text-base text-main">{{ calculateValue(item?.value) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { controlledComputed } from '@vueuse/core'
|
||||
import { controlledComputed } from '@vueuse/core';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -47,7 +47,7 @@ const isPositive = controlledComputed(
|
||||
|
||||
<div class="">
|
||||
<h6 class="text-lg text-center font-semibold mt-2 mb-1">
|
||||
{{ props.stats || '-'}}
|
||||
{{ props.stats || '-' }}
|
||||
</h6>
|
||||
<p class="text-sm text-center">
|
||||
{{ props.title }}
|
||||
|
||||
@@ -7,26 +7,26 @@ const props = defineProps({
|
||||
css: { type: String },
|
||||
});
|
||||
|
||||
const s = ref(0)
|
||||
|
||||
const s = ref(0);
|
||||
</script>
|
||||
<template>
|
||||
<Countdown
|
||||
v-if="time"
|
||||
:time="time > 0 ? time : 0"
|
||||
v-slot="{ days, hours, minutes, seconds }"
|
||||
class="countdown-container justify-items-center "
|
||||
class="countdown-container justify-items-center"
|
||||
>
|
||||
<span class="text-primary font-bold " :class="css">{{ days }}</span> days
|
||||
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
|
||||
<span class="text-primary font-bold" :class="css">{{ minutes }}</span> minutes
|
||||
<span class="text-primary font-bold w-40" :class="css">
|
||||
<Transition name="slide-up">
|
||||
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
|
||||
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
|
||||
</Transition>
|
||||
</span>
|
||||
<span class="ml-10">seconds</span>
|
||||
<span class="text-primary font-bold" :class="css">{{ days }}</span> days
|
||||
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
|
||||
<span class="text-primary font-bold" :class="css">{{ minutes }}</span>
|
||||
minutes
|
||||
<span class="text-primary font-bold w-40" :class="css">
|
||||
<Transition name="slide-up">
|
||||
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
|
||||
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
|
||||
</Transition>
|
||||
</span>
|
||||
<span class="ml-10">seconds</span>
|
||||
</Countdown>
|
||||
</template>
|
||||
|
||||
@@ -42,5 +42,4 @@ const s = ref(0)
|
||||
text-align: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -2,49 +2,57 @@
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
total: { type: String },
|
||||
limit: { type: Number },
|
||||
callback: { type: Function, required: true },
|
||||
total: { type: String },
|
||||
limit: { type: Number },
|
||||
callback: { type: Function, required: true },
|
||||
});
|
||||
const current = ref(1)
|
||||
const showSize = 3
|
||||
const current = ref(1);
|
||||
const showSize = 3;
|
||||
const pages = computed(() => {
|
||||
const pages: { color: string, page: number }[] = []
|
||||
const total = Number(props.total || 0)
|
||||
if (total > 0 && props.limit && total > props.limit) {
|
||||
let page = 0
|
||||
while (true) {
|
||||
if (page * props.limit >= total) break
|
||||
page += 1
|
||||
if (total / props.limit > 10 && page > showSize && page < (total / props.limit - showSize + 1)) {
|
||||
if (!(page >= current.value - 1 && page <= current.value + 1)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
pages.push({
|
||||
color: page === current.value ? 'btn-primary' : '',
|
||||
page: page,
|
||||
})
|
||||
const pages: { color: string; page: number }[] = [];
|
||||
const total = Number(props.total || 0);
|
||||
if (total > 0 && props.limit && total > props.limit) {
|
||||
let page = 0;
|
||||
while (true) {
|
||||
if (page * props.limit >= total) break;
|
||||
page += 1;
|
||||
if (
|
||||
total / props.limit > 10 &&
|
||||
page > showSize &&
|
||||
page < total / props.limit - showSize + 1
|
||||
) {
|
||||
if (!(page >= current.value - 1 && page <= current.value + 1)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pages.push({
|
||||
color: page === current.value ? 'btn-primary' : '',
|
||||
page: page,
|
||||
});
|
||||
}
|
||||
return pages
|
||||
})
|
||||
}
|
||||
return pages;
|
||||
});
|
||||
|
||||
function gotoPage(pageNum: number) {
|
||||
current.value = pageNum
|
||||
props.callback(pageNum)
|
||||
current.value = pageNum;
|
||||
props.callback(pageNum);
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="my-5 text-center">
|
||||
<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="{
|
||||
'!btn-primary': color === 'btn-primary',
|
||||
}" @click="gotoPage(page)">
|
||||
{{ page }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="my-5 text-center">
|
||||
<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="{
|
||||
'!btn-primary': color === 'btn-primary',
|
||||
}"
|
||||
@click="gotoPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,10 +39,12 @@ const voterStatusMap: Record<string, string> = {
|
||||
|
||||
const proposalInfo = ref();
|
||||
|
||||
function metaItem(metadata: string|undefined): { title: string; summary: string } {
|
||||
return metadata ? JSON.parse(metadata) : {}
|
||||
function metaItem(metadata: string | undefined): {
|
||||
title: string;
|
||||
summary: string;
|
||||
} {
|
||||
return metadata ? JSON.parse(metadata) : {};
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-white dark:bg-[#28334e] rounded text-sm">
|
||||
@@ -64,13 +66,17 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
|
||||
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
|
||||
>
|
||||
{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}
|
||||
{{
|
||||
item?.content?.title ||
|
||||
item?.title ||
|
||||
metaItem(item?.metadata)?.title
|
||||
}}
|
||||
</RouterLink>
|
||||
<div
|
||||
v-if="item.content"
|
||||
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>
|
||||
</td>
|
||||
@@ -149,7 +155,11 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
|
||||
<RouterLink
|
||||
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="flex-1 w-0 truncate mr-4"
|
||||
>{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}</RouterLink
|
||||
>{{
|
||||
item?.content?.title ||
|
||||
item?.title ||
|
||||
metaItem(item?.metadata)?.title
|
||||
}}</RouterLink
|
||||
>
|
||||
<label
|
||||
for="proposal-detail-modal"
|
||||
@@ -225,7 +235,6 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
|
||||
|
||||
<span v-else>Vote</span></label
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,9 +251,24 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
|
||||
<h3 class="font-bold text-lg">Description</h3>
|
||||
<p class="py-4">
|
||||
<Component
|
||||
v-if="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
|
||||
:is="select(proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary, 'horizontal')"
|
||||
:value="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
|
||||
v-if="
|
||||
proposalInfo?.content?.description ||
|
||||
proposalInfo?.summary ||
|
||||
metaItem(proposalInfo?.metadata)?.summary
|
||||
"
|
||||
:is="
|
||||
select(
|
||||
proposalInfo?.content?.description ||
|
||||
proposalInfo?.summary ||
|
||||
metaItem(proposalInfo?.metadata)?.summary,
|
||||
'horizontal'
|
||||
)
|
||||
"
|
||||
:value="
|
||||
proposalInfo?.content?.description ||
|
||||
proposalInfo?.summary ||
|
||||
metaItem(proposalInfo?.metadata)?.summary
|
||||
"
|
||||
>
|
||||
</Component>
|
||||
</p>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTxDialog, useBlockchain } from '@/stores';
|
||||
const store = useTxDialog();
|
||||
const chainStore = useBlockchain()
|
||||
const chainStore = useBlockchain();
|
||||
</script>
|
||||
<template>
|
||||
<ping-tx-dialog
|
||||
:type="store.type"
|
||||
:sender="store.sender"
|
||||
:endpoint="store.endpoint"
|
||||
:params='store.params'
|
||||
:params="store.params"
|
||||
:hd-path="store.hdPaths"
|
||||
:registry-name="chainStore.current?.prettyName || chainStore.chainName"
|
||||
@view="store.view"
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
blocks: { type: Array as PropType<{height:string, color: string}[]> },
|
||||
blocks: { type: Array as PropType<{ height: string; color: string }[]> },
|
||||
});
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-0.5">
|
||||
<div class="cursor-default" v-for="(item, index) in blocks" :key="index">
|
||||
<div class="tooltip"
|
||||
:data-tip="item.height"
|
||||
:class="item.color"
|
||||
style="width: 3px;"
|
||||
<div
|
||||
class="tooltip"
|
||||
:data-tip="item.height"
|
||||
:class="item.color"
|
||||
style="width: 3px"
|
||||
>
|
||||
|
||||
</div>
|
||||
@@ -20,6 +20,4 @@ const props = defineProps({
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
@@ -5,34 +5,34 @@ import { useFormatter } from '@/stores';
|
||||
import type { CommissionRate } from '@/types';
|
||||
|
||||
const props = defineProps({
|
||||
commission: { type: Object as PropType<CommissionRate> },
|
||||
commission: { type: Object as PropType<CommissionRate> },
|
||||
});
|
||||
|
||||
let rate = computed(
|
||||
() => Number(props.commission?.commission_rates.rate || 0) * 100
|
||||
() => Number(props.commission?.commission_rates.rate || 0) * 100
|
||||
);
|
||||
let change = computed(
|
||||
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
|
||||
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
|
||||
);
|
||||
let max = computed(
|
||||
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
|
||||
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
|
||||
);
|
||||
|
||||
const left = rate;
|
||||
const right = computed(() => max.value - rate.value);
|
||||
|
||||
const s1 = computed(() =>
|
||||
left.value > change.value ? left.value - change.value : 0
|
||||
left.value > change.value ? left.value - change.value : 0
|
||||
);
|
||||
const s2 = computed(() =>
|
||||
left.value > change.value ? change.value : left.value
|
||||
left.value > change.value ? change.value : left.value
|
||||
);
|
||||
const s3 = 2;
|
||||
const s4 = computed(() =>
|
||||
right.value > change.value ? change.value : right.value
|
||||
right.value > change.value ? change.value : right.value
|
||||
);
|
||||
const s5 = computed(() =>
|
||||
right.value > change.value ? right.value - change.value : 0
|
||||
right.value > change.value ? right.value - change.value : 0
|
||||
);
|
||||
|
||||
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
|
||||
@@ -40,107 +40,112 @@ const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
|
||||
const format = useFormatter();
|
||||
|
||||
const chartConfig = computed(() => {
|
||||
const secondaryText = `hsl(var(--bc))`;
|
||||
const primaryText = `hsl(var(--bc))`;
|
||||
|
||||
const secondaryText = `hsl(var(--bc))`;
|
||||
const primaryText = `hsl(var(--bc))`;
|
||||
|
||||
return {
|
||||
chart: {
|
||||
width: '200px',
|
||||
sparkline: { enabled: false },
|
||||
},
|
||||
colors: ['rgba(109,120,141,0.2)', 'rgba(114,225,40,0.2)', 'rgba(114,225,40,1)', 'rgba(114,225,40,0.2)', 'rgba(109,120,141,0.2)'],
|
||||
legend: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
dataLabels: { enabled: false },
|
||||
stroke: {
|
||||
width: 3,
|
||||
lineCap: 'round',
|
||||
colors: ['hsl(var(--b1))'],
|
||||
},
|
||||
labels: [
|
||||
'Available',
|
||||
'Daily Change',
|
||||
'Commission Rate',
|
||||
'Daily Change',
|
||||
'Available',
|
||||
],
|
||||
states: {
|
||||
hover: {
|
||||
filter: { type: 'none' },
|
||||
return {
|
||||
chart: {
|
||||
width: '200px',
|
||||
sparkline: { enabled: false },
|
||||
},
|
||||
colors: [
|
||||
'rgba(109,120,141,0.2)',
|
||||
'rgba(114,225,40,0.2)',
|
||||
'rgba(114,225,40,1)',
|
||||
'rgba(114,225,40,0.2)',
|
||||
'rgba(109,120,141,0.2)',
|
||||
],
|
||||
legend: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
dataLabels: { enabled: false },
|
||||
stroke: {
|
||||
width: 3,
|
||||
lineCap: 'round',
|
||||
colors: ['hsl(var(--b1))'],
|
||||
},
|
||||
labels: [
|
||||
'Available',
|
||||
'Daily Change',
|
||||
'Commission Rate',
|
||||
'Daily Change',
|
||||
'Available',
|
||||
],
|
||||
states: {
|
||||
hover: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
active: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
endAngle: 130,
|
||||
startAngle: -130,
|
||||
customScale: 0.9,
|
||||
donut: {
|
||||
size: '83%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: 25,
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
},
|
||||
active: {
|
||||
filter: { type: 'none' },
|
||||
value: {
|
||||
offsetY: -15,
|
||||
fontWeight: 500,
|
||||
fontSize: '2.125rem',
|
||||
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
|
||||
color: primaryText,
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Commission Rate',
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
formatter: () => `${rate.value.toFixed(1)}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
endAngle: 130,
|
||||
startAngle: -130,
|
||||
customScale: 0.9,
|
||||
donut: {
|
||||
size: '83%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: 25,
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
},
|
||||
value: {
|
||||
offsetY: -15,
|
||||
fontWeight: 500,
|
||||
fontSize: '2.125rem',
|
||||
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
|
||||
color: primaryText,
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Commission Rate',
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
formatter: () => `${rate.value.toFixed(1)}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1709,
|
||||
options: {
|
||||
chart: { height: 237 },
|
||||
},
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1709,
|
||||
options: {
|
||||
chart: { height: 237 },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-base-100 rounded shadow p-4">
|
||||
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
|
||||
</div>
|
||||
<div class="w-80 m-auto">
|
||||
<ApexCharts type="donut" :options="chartConfig" :series="series" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-wrap gap-x-3">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
|
||||
<span class="text-caption">24h: ±{{ change }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Max:{{ max }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-base-100 rounded shadow p-4">
|
||||
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
|
||||
</div>
|
||||
<div class="w-80 m-auto">
|
||||
<ApexCharts type="donut" :options="chartConfig" :series="series" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-wrap gap-x-3">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
|
||||
<span class="text-caption">24h: ±{{ change }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Max:{{ max }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+252
-157
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { get, post } from "@/libs/http"
|
||||
import { useBaseStore, useTxDialog } from "@/stores";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import TextElement from "@/components/dynamic/TextElement.vue";
|
||||
import { get, post } from '@/libs/http';
|
||||
import { useBaseStore, useTxDialog } from '@/stores';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import TextElement from '@/components/dynamic/TextElement.vue';
|
||||
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
||||
import { codeToHtml } from 'shiki'
|
||||
import { useWasmStore } from "@/modules/[chain]/cosmwasm/WasmStore";
|
||||
import { toBase64 } from "@cosmjs/encoding";
|
||||
import { codeToHtml } from 'shiki';
|
||||
import { useWasmStore } from '@/modules/[chain]/cosmwasm/WasmStore';
|
||||
import { toBase64 } from '@cosmjs/encoding';
|
||||
|
||||
import { JsonViewer } from "vue3-json-viewer"
|
||||
import { CosmjsOfflineSigner } from "@leapwallet/cosmos-snap-provider";
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import { CosmjsOfflineSigner } from '@leapwallet/cosmos-snap-provider';
|
||||
|
||||
interface Verification {
|
||||
chainId?: string;
|
||||
@@ -29,13 +29,13 @@ interface SourceCode<T> {
|
||||
interface Argument {
|
||||
format?: string;
|
||||
type: string;
|
||||
properties: Record<string, Argument>
|
||||
properties: Record<string, Argument>;
|
||||
}
|
||||
|
||||
interface Method {
|
||||
type: string;
|
||||
required: string[];
|
||||
properties: Record<string, Argument>
|
||||
properties: Record<string, Argument>;
|
||||
additionalProperties: boolean;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ const props = defineProps({
|
||||
contract: { type: String },
|
||||
});
|
||||
|
||||
const baseurl = "https://prod.compiler.welldonestudio.io"
|
||||
const baseurl = 'https://prod.compiler.welldonestudio.io';
|
||||
|
||||
const verification = ref<Verification>({});
|
||||
const sourceCode = ref<SourceCode<string>[]>([]);
|
||||
@@ -59,206 +59,301 @@ const baseStore = useBaseStore();
|
||||
const dialog = useTxDialog();
|
||||
const result = ref<Record<string, any>>({});
|
||||
|
||||
|
||||
function fetchVerification() {
|
||||
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`
|
||||
get(url).then((x) => {
|
||||
console.log("verification:", x)
|
||||
verification.value = x
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`;
|
||||
get(url)
|
||||
.then((x) => {
|
||||
console.log('verification:', x);
|
||||
verification.value = x;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchSchema() {
|
||||
const base = useBaseStore()
|
||||
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
|
||||
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`
|
||||
get(url).then(async (x) => {
|
||||
console.log("schema:", x)
|
||||
schemas.value = x.sourceCodes
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
function fetchSourceCode() {
|
||||
const base = useBaseStore()
|
||||
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
|
||||
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`
|
||||
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light'
|
||||
get(url).then(async (x) => {
|
||||
console.log("source codes:", x)
|
||||
for (let i = 0; i < x.sourceCodes.length; i++) {
|
||||
const sc = x.sourceCodes[i];
|
||||
sc.sourceCode = await codeToHtml(sc.sourceCode, {lang: sc.path.endsWith('.toml')?'toml':'rust', theme})
|
||||
}
|
||||
sourceCode.value = x.sourceCodes
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
const base = useBaseStore();
|
||||
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
|
||||
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`;
|
||||
get(url)
|
||||
.then(async (x) => {
|
||||
console.log('schema:', x);
|
||||
schemas.value = x.sourceCodes;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
const base = useBaseStore()
|
||||
const chain_id = ref("")
|
||||
function fetchSourceCode() {
|
||||
const base = useBaseStore();
|
||||
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
|
||||
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`;
|
||||
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light';
|
||||
get(url)
|
||||
.then(async (x) => {
|
||||
console.log('source codes:', x);
|
||||
for (let i = 0; i < x.sourceCodes.length; i++) {
|
||||
const sc = x.sourceCodes[i];
|
||||
sc.sourceCode = await codeToHtml(sc.sourceCode, {
|
||||
lang: sc.path.endsWith('.toml') ? 'toml' : 'rust',
|
||||
theme,
|
||||
});
|
||||
}
|
||||
sourceCode.value = x.sourceCodes;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
const base = useBaseStore();
|
||||
const chain_id = ref('');
|
||||
base.$subscribe((m, s) => {
|
||||
if (chain_id.value !== s.latest?.block?.header?.chain_id) {
|
||||
chain_id.value = s.latest?.block?.header?.chain_id || "unknown"
|
||||
chain_id.value = s.latest?.block?.header?.chain_id || 'unknown';
|
||||
fetchVerification();
|
||||
fetchSchema();
|
||||
fetchSourceCode();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function verify() {
|
||||
const base = useBaseStore();
|
||||
const id = base.latest?.block?.header?.chain_id || 'unknown';
|
||||
const data = { contractAddress: props.contract, chainId: id };
|
||||
|
||||
const base = useBaseStore()
|
||||
const id = base.latest?.block?.header?.chain_id || "unknown"
|
||||
const data = {"contractAddress": props.contract, "chainId": id}
|
||||
|
||||
post(`${baseurl}/verification/neutron`, data).then((x)=> {
|
||||
if(x.result) {
|
||||
verification.value = x.result
|
||||
fetchSchema();
|
||||
fetchSourceCode();
|
||||
}
|
||||
})
|
||||
post(`${baseurl}/verification/neutron`, data).then((x) => {
|
||||
if (x.result) {
|
||||
verification.value = x.result;
|
||||
fetchSchema();
|
||||
fetchSourceCode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tab = ref('verification')
|
||||
const tab = ref('verification');
|
||||
function selectTab(tabName: string) {
|
||||
tab.value = tabName
|
||||
tab.value = tabName;
|
||||
}
|
||||
|
||||
const executions = computed(() => {
|
||||
return schemas.value
|
||||
.filter(x => x.path.indexOf('execute_msg')>-1 || x.path.indexOf('query_msg')>-1)
|
||||
.map(x => JSON.parse(x.sourceCode||"{}") as Schema)
|
||||
.filter(
|
||||
(x) =>
|
||||
x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1
|
||||
)
|
||||
.map((x) => JSON.parse(x.sourceCode || '{}') as Schema);
|
||||
// if(raw && raw.sourceCode) {
|
||||
// return JSON.parse(raw.sourceCode) as Schema
|
||||
// }
|
||||
// }
|
||||
// return {} as Schema
|
||||
})
|
||||
});
|
||||
|
||||
const queries = computed(() => {
|
||||
let raw = schemas.value.find(x => x.path.indexOf('query_msg')>-1)
|
||||
if(raw && raw.sourceCode) {
|
||||
return JSON.parse(raw.sourceCode) as Schema
|
||||
}
|
||||
return {} as Schema
|
||||
})
|
||||
let raw = schemas.value.find((x) => x.path.indexOf('query_msg') > -1);
|
||||
if (raw && raw.sourceCode) {
|
||||
return JSON.parse(raw.sourceCode) as Schema;
|
||||
}
|
||||
return {} as Schema;
|
||||
});
|
||||
|
||||
function callFunction(title: string, method: string, arg: Argument) {
|
||||
if(!props.contract) return
|
||||
if (!props.contract) return;
|
||||
|
||||
// console.log("callFunction", title, method, arg)
|
||||
|
||||
let args = {} as Record<string, any>
|
||||
if(arg.properties) Object.keys(arg.properties).forEach(k => {
|
||||
const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement
|
||||
if (input) {
|
||||
args[k] = input.value
|
||||
}
|
||||
})
|
||||
let args = {} as Record<string, any>;
|
||||
if (arg.properties)
|
||||
Object.keys(arg.properties).forEach((k) => {
|
||||
const input = document.querySelector(
|
||||
`input[name="${method}-${k}"]`
|
||||
) as HTMLInputElement;
|
||||
if (input) {
|
||||
args[k] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
//console.log("args", arg.properties, JSON.stringify(args))
|
||||
|
||||
if(title === 'ExecuteMsg') {
|
||||
let execution = {} as Record<string, any>
|
||||
execution[method] = args
|
||||
console.log("execution", execution)
|
||||
dialog.open('wasm_execute_contract', { contract: props.contract, execution})
|
||||
if (title === 'ExecuteMsg') {
|
||||
let execution = {} as Record<string, any>;
|
||||
execution[method] = args;
|
||||
console.log('execution', execution);
|
||||
dialog.open('wasm_execute_contract', {
|
||||
contract: props.contract,
|
||||
execution,
|
||||
});
|
||||
} else {
|
||||
// QueryMsg
|
||||
wasmStore.wasmClient
|
||||
.getWasmContractSmartQuery(props.contract, `{"${method}": ${JSON.stringify(args)}}`)
|
||||
.getWasmContractSmartQuery(
|
||||
props.contract,
|
||||
`{"${method}": ${JSON.stringify(args)}}`
|
||||
)
|
||||
.then((x) => {
|
||||
result.value[`${title}-${method}`] = x;
|
||||
result.value[`${title}-${method}`] = x;
|
||||
})
|
||||
.catch((err) => {
|
||||
result.value[`${title}-${method}`] = err;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||
<div role="tablist" class="tabs tabs-boxed">
|
||||
<a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio">
|
||||
<div class="w-8 rounded">
|
||||
<img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio"/>
|
||||
</div>
|
||||
</a>
|
||||
<a role="tab" class="tab" :class="{'tab-active': tab==='verification'}" @click="selectTab('verification')">Verification</a>
|
||||
<a role="tab" class="tab" :class="{'tab-active': tab==='executions'}" @click="selectTab('executions')">Functions</a>
|
||||
<a role="tab" class="tab" :class="{'tab-active': tab==='source_code'}" @click="selectTab('source_code')">Source Code</a>
|
||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||
<div role="tablist" class="tabs tabs-boxed">
|
||||
<a
|
||||
role="tab"
|
||||
class="tab tooltip tooltip-right tooltip-success"
|
||||
data-tip="Powered By WELLDONE Studio"
|
||||
>
|
||||
<div class="w-8 rounded">
|
||||
<img
|
||||
src="../assets/images/welldone-logo.svg"
|
||||
alt="Powered By WELLDONE Studio"
|
||||
/>
|
||||
</div>
|
||||
<div class="">
|
||||
<div v-if="tab === 'verification'"><DynamicComponent :value="verification"/></div>
|
||||
<div v-if="tab === 'executions'" class="">
|
||||
<div v-for="{title, oneOf} in executions" class="join join-vertical w-full mt-2">
|
||||
<div v-if="oneOf" v-for="m in oneOf">
|
||||
<div v-for="(props, method) in m.properties" class="collapse collapse-arrow join-item border border-base-300">
|
||||
<input type="radio" name="my-accordion-1" :checked="false"/>
|
||||
<div class="collapse-title font-medium">
|
||||
{{title}}::{{ method }}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div v-for="(p, name) in props.properties" class="form-control pb-2">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ name }}</span>
|
||||
<span></span>
|
||||
</label>
|
||||
<input :name="`${method}-${name}`" type="text" :placeholder="p.format" class="input input-sm border border-gray-300 dark:border-gray-600 w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label v-if="title==='ExecuteMsg'" for="wasm_execute_contract" class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
|
||||
<label v-else class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
|
||||
</div>
|
||||
<div v-if="result[`${title}-${method}`]" class="mt-2">
|
||||
<JsonViewer :value="result[`${title}-${method}`]" :theme="baseStore.theme||'dark'" style="background: transparent;" copyable boxed sort :expand-depth="5"/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab"
|
||||
:class="{ 'tab-active': tab === 'verification' }"
|
||||
@click="selectTab('verification')"
|
||||
>Verification</a
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab"
|
||||
:class="{ 'tab-active': tab === 'executions' }"
|
||||
@click="selectTab('executions')"
|
||||
>Functions</a
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab"
|
||||
:class="{ 'tab-active': tab === 'source_code' }"
|
||||
@click="selectTab('source_code')"
|
||||
>Source Code</a
|
||||
>
|
||||
</div>
|
||||
<div class="">
|
||||
<div v-if="tab === 'verification'">
|
||||
<DynamicComponent :value="verification" />
|
||||
</div>
|
||||
<div v-if="tab === 'executions'" class="">
|
||||
<div
|
||||
v-for="{ title, oneOf } in executions"
|
||||
class="join join-vertical w-full mt-2"
|
||||
>
|
||||
<div v-if="oneOf" v-for="m in oneOf">
|
||||
<div
|
||||
v-for="(props, method) in m.properties"
|
||||
class="collapse collapse-arrow join-item border border-base-300"
|
||||
>
|
||||
<input type="radio" name="my-accordion-1" :checked="false" />
|
||||
<div class="collapse-title font-medium">
|
||||
{{ title }}::{{ method }}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div
|
||||
v-for="(p, name) in props.properties"
|
||||
class="form-control pb-2"
|
||||
>
|
||||
<label class="label">
|
||||
<span class="label-text">{{ name }}</span>
|
||||
<span></span>
|
||||
</label>
|
||||
<input
|
||||
:name="`${method}-${name}`"
|
||||
type="text"
|
||||
:placeholder="p.format"
|
||||
class="input input-sm border border-gray-300 dark:border-gray-600 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
v-if="title === 'ExecuteMsg'"
|
||||
for="wasm_execute_contract"
|
||||
class="btn btn-sm"
|
||||
@click="callFunction(title, method, props)"
|
||||
>{{ method }}</label
|
||||
>
|
||||
<label
|
||||
v-else
|
||||
class="btn btn-sm"
|
||||
@click="callFunction(title, method, props)"
|
||||
>{{ method }}</label
|
||||
>
|
||||
</div>
|
||||
<div v-if="result[`${title}-${method}`]" class="mt-2">
|
||||
<JsonViewer
|
||||
:value="result[`${title}-${method}`]"
|
||||
:theme="baseStore.theme || 'dark'"
|
||||
style="background: transparent"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
:expand-depth="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
|
||||
<div v-for="sc in sourceCode.filter(x => !x.isDirectory)" class="collapse collapse-arrow join-item border border-base-300">
|
||||
<input type="radio" name="sourceCodeAccordion" :checked="false"/>
|
||||
<div class="collapse-title font-medium">{{sc.path}}</div>
|
||||
<div class="collapse-content overflow-auto" v-html="sc.sourceCode"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="tab === 'verification'" class="text-center">
|
||||
<div v-if="Object.keys(verification).length == 0" >
|
||||
Haven't found verification
|
||||
</div>
|
||||
<button class="btn btn-primary mt-5" @click="verify" v-show="tab === 'verification'" :disabled="verification.error !== undefined">verify</button>
|
||||
</div>
|
||||
|
||||
<!-- alert-info -->
|
||||
</div>
|
||||
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
|
||||
<div
|
||||
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
|
||||
v-for="sc in sourceCode.filter((x) => !x.isDirectory)"
|
||||
class="collapse collapse-arrow join-item border border-base-300"
|
||||
>
|
||||
<input type="radio" name="sourceCodeAccordion" :checked="false" />
|
||||
<div class="collapse-title font-medium">{{ sc.path }}</div>
|
||||
<div
|
||||
class="drop-shadow-md px-4 pt-2 pb-2"
|
||||
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
|
||||
>
|
||||
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
|
||||
</div>
|
||||
<div class="px-4 py-4">
|
||||
<ul style="list-style-type: disc" class="pl-8">
|
||||
<li>
|
||||
{{ $t('cosmwasm.tips_description_1') }}
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.welldonestudio.io/code/verification-api/" target="_blank">Link to Verification API Manual</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
class="collapse-content overflow-auto"
|
||||
v-html="sc.sourceCode"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="tab === 'verification'" class="text-center">
|
||||
<div v-if="Object.keys(verification).length == 0">
|
||||
Haven't found verification
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary mt-5"
|
||||
@click="verify"
|
||||
v-show="tab === 'verification'"
|
||||
:disabled="verification.error !== undefined"
|
||||
>
|
||||
verify
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- alert-info -->
|
||||
<div
|
||||
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
|
||||
>
|
||||
<div
|
||||
class="drop-shadow-md px-4 pt-2 pb-2"
|
||||
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
|
||||
>
|
||||
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
|
||||
</div>
|
||||
<div class="px-4 py-4">
|
||||
<ul style="list-style-type: disc" class="pl-8">
|
||||
<li>
|
||||
{{ $t('cosmwasm.tips_description_1') }}
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://docs.welldonestudio.io/code/verification-api/"
|
||||
target="_blank"
|
||||
>Link to Verification API Manual</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
|
||||
const aa = computed(() => {
|
||||
const hostname = location.hostname
|
||||
const hostname = location.hostname;
|
||||
if (hostname === 'testnet.ping.pub') {
|
||||
return '2396360'
|
||||
return '2396360';
|
||||
} else if (hostname === 'ping.pub') {
|
||||
return '2395639'
|
||||
return '2395639';
|
||||
} else {
|
||||
return '2396360'
|
||||
return '2396360';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
</template>
|
||||
<template></template>
|
||||
@@ -8,51 +8,49 @@ import { useBaseStore } from '@/stores';
|
||||
const store = useIndexModule();
|
||||
const baseStore = useBaseStore();
|
||||
const chartConfig = computed(() => {
|
||||
const theme = baseStore.theme;
|
||||
const labels = store.marketData.prices.map((item: any) => item[0]);
|
||||
return getMarketPriceChartConfig(theme, 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: kind.value === 'price' ? 'Price' : 'Volume',
|
||||
data:
|
||||
kind.value === 'price'
|
||||
? store.marketData.prices.map((item: any) => item[1])
|
||||
: store.marketData.total_volumes.map(
|
||||
(item: any) => item[1]
|
||||
),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: kind.value === 'price' ? 'Price' : 'Volume',
|
||||
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>
|
||||
@@ -6,39 +6,43 @@ import { useBaseStore } from '@/stores';
|
||||
const baseStore = useBaseStore();
|
||||
|
||||
const options = computed(() => {
|
||||
return {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 150
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
// borderRadius: 4,
|
||||
horizontal: false,
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#5A67D8'],
|
||||
xaxis: {
|
||||
labels: {
|
||||
show: false,
|
||||
rotate: -45
|
||||
},
|
||||
show: false,
|
||||
categories: baseStore.recents.map((x) => x.block.header.height).concat(Array(50-baseStore.recents.length).fill('')),
|
||||
}
|
||||
};
|
||||
return {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 150,
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
// borderRadius: 4,
|
||||
horizontal: false,
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
colors: ['#5A67D8'],
|
||||
xaxis: {
|
||||
labels: {
|
||||
show: false,
|
||||
rotate: -45,
|
||||
},
|
||||
show: false,
|
||||
categories: baseStore.recents
|
||||
.map((x) => x.block.header.height)
|
||||
.concat(Array(50 - baseStore.recents.length).fill('')),
|
||||
},
|
||||
};
|
||||
});
|
||||
const series = computed(() => {
|
||||
return [{
|
||||
name: 'Txs',
|
||||
data: baseStore.recents?.map((x) => x.block.data.txs.length) || []
|
||||
}]
|
||||
return [
|
||||
{
|
||||
name: 'Txs',
|
||||
data: baseStore.recents?.map((x) => x.block.data.txs.length) || [],
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ApexCharts type="bar" height="150" :options="options" :series="series" />
|
||||
<ApexCharts type="bar" height="150" :options="options" :series="series" />
|
||||
</template>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useBlockchain } from '@/stores';
|
||||
import numeral from 'numeral';
|
||||
|
||||
const chainStore = useBlockchain()
|
||||
const chainStore = useBlockchain();
|
||||
|
||||
const themeColors = (theme: string) => {
|
||||
if (theme === 'light') {
|
||||
@@ -260,14 +260,55 @@ export const getMarketPriceChartConfig = (
|
||||
};
|
||||
|
||||
// const donutColors = Array.from({length: 19}, () => (`#${Math.floor(Math.random()*16777215+100000).toString(16)}`))
|
||||
const donutColors = ["#bbe81a", "#ff5f0b", "#43ebef", "#1999e5", "#230b2c", "#628be8", "#aa5343", "#c9fa89", "#e88ea8", "#72e4a2", "#38cd87", "#515e13", "#7bf8f5", "#83dd6e", "#e8b203", "#7d11d5", "#3e4927", "#f303e2", "#249493", "#50e5e6", "#11deb2", "#a2f9c7", "#2a7bdc", "#47383a", "#226da4", "#966319", "#1bdf99", "#f3ab0c", "#961f50", "#832efd", "#875287", "#4bebe7", "#1d3d2e", "#9caea4", "#2772f5", "#938bf1", "#6228a5", "#24fea5", "#c9bbc8", "#e27225", "#54bd9f", "#babb2d", "#bcf591", "#803b36", "#124f03"]
|
||||
|
||||
|
||||
export const getDonutChartConfig = (
|
||||
theme: string,
|
||||
labels: string[]
|
||||
) => {
|
||||
const donutColors = [
|
||||
'#bbe81a',
|
||||
'#ff5f0b',
|
||||
'#43ebef',
|
||||
'#1999e5',
|
||||
'#230b2c',
|
||||
'#628be8',
|
||||
'#aa5343',
|
||||
'#c9fa89',
|
||||
'#e88ea8',
|
||||
'#72e4a2',
|
||||
'#38cd87',
|
||||
'#515e13',
|
||||
'#7bf8f5',
|
||||
'#83dd6e',
|
||||
'#e8b203',
|
||||
'#7d11d5',
|
||||
'#3e4927',
|
||||
'#f303e2',
|
||||
'#249493',
|
||||
'#50e5e6',
|
||||
'#11deb2',
|
||||
'#a2f9c7',
|
||||
'#2a7bdc',
|
||||
'#47383a',
|
||||
'#226da4',
|
||||
'#966319',
|
||||
'#1bdf99',
|
||||
'#f3ab0c',
|
||||
'#961f50',
|
||||
'#832efd',
|
||||
'#875287',
|
||||
'#4bebe7',
|
||||
'#1d3d2e',
|
||||
'#9caea4',
|
||||
'#2772f5',
|
||||
'#938bf1',
|
||||
'#6228a5',
|
||||
'#24fea5',
|
||||
'#c9bbc8',
|
||||
'#e27225',
|
||||
'#54bd9f',
|
||||
'#babb2d',
|
||||
'#bcf591',
|
||||
'#803b36',
|
||||
'#124f03',
|
||||
];
|
||||
|
||||
export const getDonutChartConfig = (theme: string, labels: string[]) => {
|
||||
const { themeSecondaryTextColor, themePrimaryTextColor } =
|
||||
colorVariables(theme);
|
||||
|
||||
@@ -359,4 +400,3 @@ export const getDonutChartConfig = (
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ const props = defineProps({
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item,index) of props.value" :key="index">
|
||||
<div v-for="(item, index) of props.value" :key="index">
|
||||
{{ toBase64(item) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import ArrayBytesElement from './ArrayBytesElement.vue';
|
||||
import ArrayObjectElement from './ArrayObjectElement.vue';
|
||||
import TextElement from './TextElement.vue';
|
||||
import ArrayCoinElement from './ArrayCoinElement.vue';
|
||||
import ArrayStringElement from './ArrayStringElement.vue'
|
||||
import ArrayStringElement from './ArrayStringElement.vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: Array<Object> },
|
||||
|
||||
@@ -18,7 +18,7 @@ const header = computed(() => {
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="overflow-auto max-h-96 ">
|
||||
<div class="overflow-auto max-h-96">
|
||||
<table class="table table-xs table-compact table-pin-rows w-full">
|
||||
<thead v-if="thead">
|
||||
<tr>
|
||||
|
||||
@@ -8,13 +8,11 @@ const props = defineProps(['value']);
|
||||
<table class="table table-compact w-full text-sm">
|
||||
<tbody>
|
||||
<tr v-for="(v, k) of value">
|
||||
<td
|
||||
class="capitalize whitespace-break-spaces min-w-max"
|
||||
>
|
||||
<td class="capitalize whitespace-break-spaces min-w-max">
|
||||
{{ String(k).replaceAll('_', ' ') }}
|
||||
</td>
|
||||
<td class="w-4/5">
|
||||
<div class="overflow-hidden w-auto whitespace-normal" >
|
||||
<div class="overflow-hidden w-auto whitespace-normal">
|
||||
<Component
|
||||
v-if="v"
|
||||
:is="select(v, 'horizontal')"
|
||||
|
||||
@@ -6,15 +6,16 @@ import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { fromBase64, toHex } from '@cosmjs/encoding';
|
||||
|
||||
import { registry as nameMatcha } from "@leapwallet/name-matcha"
|
||||
import { registry as nameMatcha } from '@leapwallet/name-matcha';
|
||||
|
||||
const chainStore = useBlockchain()
|
||||
const chainStore = useBlockchain();
|
||||
const props = defineProps(['value']);
|
||||
const format = useFormatter();
|
||||
function isMD() {
|
||||
if (
|
||||
props.value &&
|
||||
(String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)
|
||||
(String(props.value).indexOf('\n') > -1 ||
|
||||
String(props.value).indexOf('\\n') > -1)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -22,39 +23,44 @@ function isMD() {
|
||||
}
|
||||
|
||||
function isAddress() {
|
||||
return isBech32Address(props.value) && String(props.value).indexOf('valoper1') === -1
|
||||
return (
|
||||
isBech32Address(props.value) &&
|
||||
String(props.value).indexOf('valoper1') === -1
|
||||
);
|
||||
}
|
||||
|
||||
const text = computed(() => {
|
||||
if(!props.value) return ""
|
||||
const v = String(props.value)
|
||||
switch(true) {
|
||||
case v.length === 28 && v.endsWith("="): {
|
||||
return format.validator(v) || v
|
||||
if (!props.value) return '';
|
||||
const v = String(props.value);
|
||||
switch (true) {
|
||||
case v.length === 28 && v.endsWith('='): {
|
||||
return format.validator(v) || v;
|
||||
}
|
||||
// 2023-06-12T03:09:38.253756368Z
|
||||
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: {
|
||||
return new Date(v).toLocaleString(navigator.language)
|
||||
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) >
|
||||
-1: {
|
||||
return new Date(v).toLocaleString(navigator.language);
|
||||
}
|
||||
case toHexOutput.value:
|
||||
return toHex(fromBase64(v)).toUpperCase()
|
||||
return toHex(fromBase64(v)).toUpperCase();
|
||||
}
|
||||
return v
|
||||
})
|
||||
return v;
|
||||
});
|
||||
|
||||
const names = ref([] as {name?: any, provider?: string}[])
|
||||
const names = ref([] as { name?: any; provider?: string }[]);
|
||||
|
||||
onMounted(() => {
|
||||
if(isAddress()) nameMatcha.lookupAll(props.value).then(re => {
|
||||
|
||||
names.value = Object.keys(re).map(key => ({name: re[key], provider: key})).filter( x => x.name)
|
||||
})
|
||||
})
|
||||
const toHexOutput = ref(false)
|
||||
if (isAddress())
|
||||
nameMatcha.lookupAll(props.value).then((re) => {
|
||||
names.value = Object.keys(re)
|
||||
.map((key) => ({ name: re[key], provider: key }))
|
||||
.filter((x) => x.name);
|
||||
});
|
||||
});
|
||||
const toHexOutput = ref(false);
|
||||
const isConvertable = computed(() => {
|
||||
return String(props.value).endsWith('=') && props.value.length !== 28
|
||||
})
|
||||
|
||||
return String(props.value).endsWith('=') && props.value.length !== 28;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<MdEditor
|
||||
@@ -64,32 +70,67 @@ const isConvertable = computed(() => {
|
||||
class="md-editor-recover"
|
||||
></MdEditor>
|
||||
<span v-else-if="isAddress()" class="flex">
|
||||
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink>
|
||||
<div v-for="{name, provider} in names">
|
||||
<span class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip" :data-tip="provider" :title="provider">
|
||||
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{
|
||||
text
|
||||
}}</RouterLink>
|
||||
<div v-for="{ name, provider } in names">
|
||||
<span
|
||||
class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip"
|
||||
:data-tip="provider"
|
||||
:title="provider"
|
||||
>
|
||||
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-success"></span>
|
||||
<button>{{ name }}</button>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else class="flex"><span class="break-words max-w-5xl">{{ text }}</span>
|
||||
<span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</span>
|
||||
<span v-else class="flex"
|
||||
><span class="break-words max-w-5xl">{{ text }}</span>
|
||||
<span
|
||||
v-if="isConvertable"
|
||||
@click="toHexOutput = !toHexOutput"
|
||||
class="ml-2 cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.md-editor-recover {
|
||||
.h1,h1 {
|
||||
.h1,
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.h2,h2 {
|
||||
.h2,
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
@@ -104,10 +145,15 @@ const isConvertable = computed(() => {
|
||||
margin-inline-end: 0px;
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
dl, ol, ul {
|
||||
dl,
|
||||
ol,
|
||||
ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
address, dl, ol, ul {
|
||||
address,
|
||||
dl,
|
||||
ol,
|
||||
ul {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p {
|
||||
@@ -115,11 +161,21 @@ const isConvertable = computed(() => {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
a {
|
||||
color: #666cff !important;
|
||||
color: #666cff !important;
|
||||
}
|
||||
.h1 > a, .h2> a, .h3> a, .h4> a, .h5> a, .h6> a, h1> a, h2> a, h3> a, h4> a, h5> a, h6> a {
|
||||
.h1 > a,
|
||||
.h2 > a,
|
||||
.h3 > a,
|
||||
.h4 > a,
|
||||
.h5 > a,
|
||||
.h6 > a,
|
||||
h1 > a,
|
||||
h2 > a,
|
||||
h3 > a,
|
||||
h4 > a,
|
||||
h5 > a,
|
||||
h6 > a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const props = defineProps(['value']);
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="overflow-auto">
|
||||
{{ value["amount"] }} {{ value["denom"] }}
|
||||
</div>
|
||||
<div class="overflow-auto">{{ value['amount'] }} {{ value['denom'] }}</div>
|
||||
</template>
|
||||
@@ -9,21 +9,23 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const txs = computed(() => {
|
||||
return props.value?.map((x) => {
|
||||
const tx_bytes = fromBase64(x);
|
||||
let tx = null
|
||||
let injected = 'Standard'
|
||||
try {
|
||||
tx = decodeTxRaw(fromBase64(x))
|
||||
} catch(e) {
|
||||
injected = 'Injected'
|
||||
}
|
||||
return {
|
||||
hash: hashTx(tx_bytes),
|
||||
tx,
|
||||
injected
|
||||
}
|
||||
}) || []
|
||||
return (
|
||||
props.value?.map((x) => {
|
||||
const tx_bytes = fromBase64(x);
|
||||
let tx = null;
|
||||
let injected = 'Standard';
|
||||
try {
|
||||
tx = decodeTxRaw(fromBase64(x));
|
||||
} catch (e) {
|
||||
injected = 'Injected';
|
||||
}
|
||||
return {
|
||||
hash: hashTx(tx_bytes),
|
||||
tx,
|
||||
injected,
|
||||
};
|
||||
}) || []
|
||||
);
|
||||
});
|
||||
|
||||
const format = useFormatter();
|
||||
@@ -35,7 +37,7 @@ const chain = useBlockchain();
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th style="position: relative; z-index: 2;">Hash</th>
|
||||
<th style="position: relative; z-index: 2">Hash</th>
|
||||
<th>Msgs</th>
|
||||
<th>Memo</th>
|
||||
</tr>
|
||||
@@ -44,10 +46,13 @@ const chain = useBlockchain();
|
||||
<tr v-for="item in txs">
|
||||
<td>{{ item.injected }}</td>
|
||||
<td>
|
||||
<span v-if="item.injected ==='Injected'">{{ item.hash }}</span>
|
||||
<RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
|
||||
item.hash
|
||||
}}</RouterLink>
|
||||
<span v-if="item.injected === 'Injected'">{{ item.hash }}</span>
|
||||
<RouterLink
|
||||
v-else
|
||||
:to="`/${chain.chainName}/tx/${item.hash}`"
|
||||
class="text-primary dark:invert"
|
||||
>{{ item.hash }}</RouterLink
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="item.tx">
|
||||
@@ -58,7 +63,9 @@ const chain = useBlockchain();
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
<td><span v-if="item.tx">{{ item.tx.body.memo }}</span></td>
|
||||
<td>
|
||||
<span v-if="item.tx">{{ item.tx.body.memo }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -31,7 +31,9 @@ function selectObject(v: Object, direct?: string) {
|
||||
return UInt8Array;
|
||||
case Array.isArray(v):
|
||||
return ArrayElement;
|
||||
case v && Object.keys(v).includes('amount') && Object.keys(v).includes('denom'): {
|
||||
case v &&
|
||||
Object.keys(v).includes('amount') &&
|
||||
Object.keys(v).includes('denom'): {
|
||||
return TokenElement;
|
||||
}
|
||||
case direct === 'horizontal':
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" tag="i" class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
tag="i"
|
||||
class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Reference in new issue
Block a user