finish home page

This commit is contained in:
liangping 2023-02-12 20:09:10 +08:00
parent ed266f5d2c
commit cfa8d07045
16 changed files with 368 additions and 37 deletions

View File

@ -15,6 +15,7 @@
"@casl/vue": "^2.2.1",
"@floating-ui/dom": "^1.2.0",
"@iconify/vue": "^4.1.0",
"@ping-pub/chain-registry-client": "^0.0.25",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vueuse/core": "^9.12.0",
"@vueuse/math": "^9.12.0",

View File

@ -0,0 +1,57 @@
<script lang="ts" setup>
import { getLogo, useDashboard, } from '@/stores/useDashboard';
import { computed } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
},
});
const dashboardStore = useDashboard()
const conf = computed(()=> dashboardStore.chains[props.name] || {})
const logoPath = computed(() => {
return getLogo(conf.value.logo_URIs)
})
</script>
<template>
<VCard outlined class="p-1">
<VList class="card-list">
<VListItem :to="`/${name}`">
<template #prepend>
<VAvatar rounded size="45" variant="tonal" class="me-3">
<VImg :src="logoPath" height="22"/>
</VAvatar>
</template>
<VListItemTitle class="font-weight-semibold text-sm mb-1">
{{ conf?.pretty_name || props.name }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
{{conf?.chain_id || ''}}
</VListItemSubtitle>
<template #append>
<VListItemAction @click="(e:Event)=>{e.stopPropagation()}">
<VCheckbox
v-model="dashboardStore.favorite"
true-icon="mdi-star"
false-icon="mdi-star"
color="warning"
:value="props.name"
/>
<VTooltip
activator="parent"
location="top"
>
Add to favorite
</VTooltip>
</VListItemAction>
</template>
</VListItem>
</VList>
</VCard>
</template>

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
import navItems from '@/layouts/navigation'
import { useThemeConfig } from '@/plugins/vuetify/@core/composable/useThemeConfig'
// Components
@ -7,6 +6,9 @@ import Footer from '@/layouts/components/Footer.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import type { VerticalNavItems } from '@/@layouts/types'
import { useDashboard } from '@/stores/useDashboard'
// @layouts plugin
import { VerticalNavLayout } from '@layouts'
@ -19,10 +21,15 @@ const verticalNavHeaderActionAnimationName = ref<null | 'rotate-180' | 'rotate-b
watch(isVerticalNavCollapsed, val => {
verticalNavHeaderActionAnimationName.value = val ? 'rotate-180' : 'rotate-back-180'
})
const dashboard = useDashboard()
dashboard.initial()
</script>
<template>
<VerticalNavLayout :nav-items="navItems">
<VerticalNavLayout :nav-items="dashboard.computeChainNav">
<!-- 👉 navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center">

View File

@ -6,10 +6,11 @@ import vuetify from "@/plugins/vuetify";
import { loadFonts } from "@/plugins/vuetify/webfontloader";
import "@/plugins/vuetify/@core/scss/template/index.scss";
import "@/plugins/vuetify/styles/styles.scss";
import { createApp } from "vue";
import { createApp, markRaw } from "vue";
import { createPinia } from "pinia";
import router from "./router";
import { useDashboard } from "./stores/useDashboard";
// import router from "@/plugins/vuetify/router";
import router from "./router";
loadFonts();
@ -19,8 +20,9 @@ const app = createApp(App);
// Use plugins
app.use(vuetify);
app.use(createPinia());
app.use(router);
app.use(layoutsPlugin);
app.use(router);
// Mount vue app
app.mount("#app");

View File

@ -0,0 +1,3 @@
<template>
<div>Hello module index</div>
</template>

View File

@ -1,25 +1,35 @@
<template>
<div>
<VCard
class="mb-6"
title="Kick start your project 🚀"
>
<VCardText>All the best for your new project.</VCardText>
<VCardText>
Please make sure to read our <a
href="https://pixinvent.com/demo/materialize-vuejs-admin-dashboard-template/documentation/"
target="_blank"
rel="noopener noreferrer"
class="text-decoration-none"
>
Template Documentation
</a> to understand where to go from here and how to use our template.
</VCardText>
</VCard>
<script lang="ts" setup>
import { useDashboard, LoadingStatus } from '@/stores/useDashboard';
import ChainSummary from '@/components/ChainSummary.vue';
<VCard title="Want to integrate JWT? 🔒">
<VCardText>We carefully crafted JWT flow so you can implement JWT with ease and with minimum efforts.</VCardText>
<VCardText>Please read our JWT Documentation to get more out of JWT authentication.</VCardText>
</VCard>
const dashboard = useDashboard()
dashboard.$subscribe((mutation, state) => {
localStorage.setItem('favorite', JSON.stringify(state.favorite))
})
</script>
<template>
<div class="d-flex flex-column align-center">
<div class="d-flex justify-center align-center align-self-center p-1 b1">
<VImg src="/logo.svg" width="85" height="85"/>
<h1 class="text-primary text-h3 font-weight-bold d-none d-md-block ml-1">
Ping Dashboard<VChip>Beta</VChip>
</h1>
</div>
<p class="mb-1">
Ping Dashboard is not just an explorer but also a wallet and more ... 🛠
</p>
<h2 class="mb-9">
Cosmos Ecosystem Blockchains 🚀
</h2>
<VProgressLinear v-if="dashboard.status !== LoadingStatus.Loaded " indeterminate color="primary darken-2"/>
<VRow>
<VCol v-for="k in dashboard.chains" md="3">
<VLazy min-height="40" min-width="200" transition="fade-transition">
<ChainSummary :name="k.chain_name" />
</VLazy>
</VCol>
</VRow>
</div>
</template>

View File

@ -22,11 +22,14 @@ $vertical-nav-horizontal-padding-start: utils.get-first-value($vertical-nav-hori
"default": (
"--v-theme-background": (
"light": (247,247,249),
"dark": (40,42,66),
// "dark": (40,42,66),
"dark": (22, 29, 49)
),
"--v-theme-surface": (
"light": (255, 255, 255),
"dark": (48,51,78),
"dark": (40,51,78),
// "dark": (48,51,78),
// "dark": #283046
),
),
"bordered": (

View File

@ -41,6 +41,8 @@ const resolveNavItemComponent = (item: NavLink | NavSectionTitle | NavGroup) =>
return VerticalNavLink
}
/*
Close overlay side when route is changed
Close overlay vertical nav when link is clicked

View File

@ -39,7 +39,7 @@ const isVerticalNavHovered = inject(injectionKeyIsVerticalNavHovered, ref(false)
// })
const isGroupActive = ref(false)
const isGroupOpen = ref(false)
const isGroupOpen = ref(true)
/**
* Checks if any of children group is open or not.
@ -77,7 +77,9 @@ watch(() => route.path, () => {
const isActive = isNavGroupActive(props.item.children, router)
// Don't open group if vertical nav is collapsed and window size is more than overlay nav breakpoint
isGroupOpen.value = isActive && !isVerticalNavMini(windowWidth, isVerticalNavHovered).value
if(props.item.badgeContent) {
isGroupOpen.value = isActive && !isVerticalNavMini(windowWidth, isVerticalNavHovered).value
}
isGroupActive.value = isActive
}, { immediate: true })
@ -135,7 +137,7 @@ watch(openGroups, val => {
if (isAnyChildOpen(props.item.children))
return
isGroupOpen.value = isActive
// isGroupOpen.value = isActive
isGroupActive.value = isActive
}, { deep: true })

View File

@ -52,9 +52,10 @@ export const isNavLinkActive = (link: NavLink, router: Router) => {
if (!resolveRoutedName)
return false
return matchedRoutes.some(route => {
return route.name === resolveRoutedName || route.meta.navActiveLink === resolveRoutedName
})
return false
// return matchedRoutes.some(route => {
// return route.name === resolveRoutedName || route.meta.navActiveLink === resolveRoutedName
// })
}
/**

View File

@ -1,12 +1,25 @@
import { setupLayouts } from "virtual:generated-layouts";
import { createRouter, createWebHistory } from "vue-router";
import { useDashboard } from "@/stores/useDashboard";
import routes from "~pages";
// import { useDashboard } from "@/stores/useDashboard";
// const dashboard = useDashboard()
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [...setupLayouts(routes)],
});
//update current blockchain
router.beforeEach((to) => {
const { chain } = to.params
if(chain){
const dashboard = useDashboard()
dashboard.setCurrentChain(chain.toString())
}
})
// Docs: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
export default router;

View File

View File

@ -0,0 +1,12 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
const route = useRoute()
console.log('route', route)
export const useBlockchain = defineStore("blockchain", () => {
const current = ref('');
return { current };
});

View File

@ -0,0 +1,191 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
import { get } from '../libs/http'
import type { Chain, Asset } from '@ping-pub/chain-registry-client/dist/types'
import type { VerticalNavItems } from '@/@layouts/types'
import ChainRegistryClient from '@ping-pub/chain-registry-client'
export interface DirectoryChain {
assets: Asset[],
bech32_prefix: string,
best_apis: {
rest: {address: string, provider: string}[]
rpc: {address: string, provider: string}[]
},
chain_id: string,
chain_name: string,
pretty_name: string,
coingecko_id: string,
cosmwasm_enabled: boolean,
decimals: number,
denom: string,
display: string,
explorers: {
name?: string | undefined;
kind?: string | undefined;
url?: string | undefined;
tx_page?: string | undefined;
account_page?: string | undefined;
}[] | undefined,
height: number,
image: string,
name: string,
network_type: string,
symbol: string,
versions: {
application_version: string,
cosmos_sdk_version: string,
tendermint_version: string,
}
}
function pathConvert(path: string | undefined) {
if(path) {
path = path.replace('https://raw.githubusercontent.com/cosmos/chain-registry/master', 'https://registry.ping.pub')
}
return path
}
export function getLogo(conf: {
svg?: string,
png?: string,
jpeg?: string,
} | undefined) {
if(conf) {
return pathConvert(conf.svg || conf.png || conf.jpeg)
}
return undefined
}
function createChainFromDirectory(source: DirectoryChain) : Chain {
const conf: Chain = {} as Chain;
conf.apis = source.best_apis
conf.bech32_prefix = source.bech32_prefix
conf.chain_id = source.chain_id
conf.chain_name = source.chain_name
conf.explorers = source.explorers
conf.pretty_name = source.pretty_name
if(source.versions) {
conf.codebase = {
recommended_version: source.versions.application_version,
cosmos_sdk_version: source.versions.cosmos_sdk_version,
tendermint_version: source.versions.tendermint_version,
}
}
if(source.image) {
conf.logo_URIs = {
svg: source.image
}
}
return conf
}
export enum LoadingStatus {
Empty,
Loading,
Loaded,
}
export enum ConfigSource {
MainnetCosmosDirectory = "https://chains.cosmos.directory",
TestnetCosmosDirectory = "https://chains.testcosmos.directory",
Local = './src/blockchain',
}
export const useDashboard = defineStore("dashboard", () => {
const status = ref(LoadingStatus.Empty)
// current blockchain for display
const source = ref(ConfigSource.MainnetCosmosDirectory)
const favorite = ref(JSON.parse(localStorage.getItem('favorite') || '["cosmoshub", "osmosis"]') as string[])
const current = ref(favorite.value[0])
const chains = ref({} as Record<string, Chain>);
const findChainByName = computed((name) => chains.value[name]);
const computeChainNav = computed(() => {
const currChain = chains.value[current.value]
let currNavItem: VerticalNavItems = []
if(currChain) {
currNavItem = [{
title: currChain.pretty_name || currChain.chain_name || current.value,
icon: {image: getLogo(currChain.logo_URIs), size: '22'},
children: [
{
title: 'Dashboard',
to: { path: `/${current.value}`},
icon: { icon: 'mdi-chevron-right', size: '22'}
}
]
}]
}
const favNavItems: VerticalNavItems = []
favorite.value.forEach(name => {
const ch = chains.value[name]
if(ch) {
favNavItems.push({
title: ch.pretty_name || ch.chain_name || name,
to: { path: `/${ch.chain_name || name}`},
icon: {image: getLogo(ch.logo_URIs), size: '22'}
} )
}
})
return [...currNavItem,
{ heading: 'Ecosystem' },
{
title: 'Favorite',
children: favNavItems,
badgeContent: favorite.value.length,
badgeClass: 'bg-success',
icon: { icon: 'mdi-star', size: '22'}
},
{
title: 'All Blockchains',
to: { path : '/'},
badgeContent: length.value,
badgeClass: 'bg-success',
icon: { icon: 'mdi-grid', size: '22'}
}
]
})
const length = computed(()=> {
return Object.keys(chains.value).length
})
async function initial() {
await loadingFromRegistry()
}
async function loadingChainByName(name: string) {
// const chain = await client.fetchChainInfo(name)
// chains.value[name] = chain
}
function loadingFromRegistry() {
if(status.value === LoadingStatus.Empty) {
status.value = LoadingStatus.Loading
get(source.value).then((res)=> {
res.chains.forEach(( x: DirectoryChain ) => {
chains.value[x.chain_name] = createChainFromDirectory(x)
});
status.value = LoadingStatus.Loaded
})
}
}
function setCurrentChain(name: string) {
if(name && name !== current.value) {
current.value = name
}
}
function setConfigSource(newSource: ConfigSource) {
source.value = newSource
initial()
}
return {
// states
status, favorite, current, chains, length,
// getters
computeChainNav, findChainByName,
// actions
initial, loadingFromRegistry, loadingChainByName, setCurrentChain, setConfigSource
};
});

View File

@ -1,6 +1,6 @@
import { breakpointsVuetify } from '@vueuse/core'
import { VIcon } from 'vuetify/components'
import { VAvatar } from 'vuetify/components'
// ❗ Logo SVG must be imported with ?raw suffix
// import logo from '@/assets/logo.svg?raw'
@ -24,7 +24,7 @@ export const { themeConfig, layoutConfig } = defineThemeConfig({
isRtl: false,
skin: Skins.Default,
routeTransition: RouteTransitions.Fade,
iconRenderer: VIcon,
iconRenderer: VAvatar,
},
navbar: {
type: NavbarType.Sticky,

View File

@ -243,6 +243,13 @@
"@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-typescript" "^7.20.0"
"@babel/runtime@^7.19.4":
version "7.20.13"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/standalone@^7.20.12":
version "7.20.15"
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.20.15.tgz#ef82f1a9789d21d8b23f74d9fa8acecbe6ced02c"
@ -294,6 +301,13 @@
resolved "https://registry.yarnpkg.com/@casl/vue/-/vue-2.2.1.tgz#decb6966a982dabd41c26379692dfde2bf67793a"
integrity sha512-1OeGhT4A7VBkEACacF2ZlHkPiFJvyFy9h2PhBnMoetFDojMHbrn3ZjKgL5zQ4wSIrTQo9KbbzG3f0uAei2GKCQ==
"@chain-registry/types@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.14.0.tgz#43ea04992adabdee2a0f03f8a519b01722ab354b"
integrity sha512-TlIqc3CijT734no7RiYBfUvCG2fory0blwrBcK4XTYOCi2vANsxfDdiPLFQcaSETYDd14DdjhrdXwMocEeOnLQ==
dependencies:
"@babel/runtime" "^7.19.4"
"@esbuild/android-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
@ -1546,6 +1560,14 @@
dependencies:
esquery "^1.0.1"
"@ping-pub/chain-registry-client@^0.0.25":
version "0.0.25"
resolved "https://registry.yarnpkg.com/@ping-pub/chain-registry-client/-/chain-registry-client-0.0.25.tgz#fcb974bdc0ef40db26425d8bf5c382aafb884f44"
integrity sha512-33foemlTE5pDMhmRN2MhtIjW0/6f4x0kR5nNneocP1ycsTwv9RgEbsjq30TFmOTW8UC1AgoQupe+LKVYHYbyBA==
dependencies:
"@chain-registry/types" "^0.14.0"
cross-fetch "^3.1.5"
"@rollup/pluginutils@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
@ -6238,6 +6260,11 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regexp.prototype.flags@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"