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", "@casl/vue": "^2.2.1",
"@floating-ui/dom": "^1.2.0", "@floating-ui/dom": "^1.2.0",
"@iconify/vue": "^4.1.0", "@iconify/vue": "^4.1.0",
"@ping-pub/chain-registry-client": "^0.0.25",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vitejs/plugin-vue-jsx": "^3.0.0",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^9.12.0",
"@vueuse/math": "^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> <script lang="ts" setup>
import navItems from '@/layouts/navigation'
import { useThemeConfig } from '@/plugins/vuetify/@core/composable/useThemeConfig' import { useThemeConfig } from '@/plugins/vuetify/@core/composable/useThemeConfig'
// Components // Components
@ -7,6 +6,9 @@ import Footer from '@/layouts/components/Footer.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue' import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue' import UserProfile from '@/layouts/components/UserProfile.vue'
import type { VerticalNavItems } from '@/@layouts/types'
import { useDashboard } from '@/stores/useDashboard'
// @layouts plugin // @layouts plugin
import { VerticalNavLayout } from '@layouts' import { VerticalNavLayout } from '@layouts'
@ -19,10 +21,15 @@ const verticalNavHeaderActionAnimationName = ref<null | 'rotate-180' | 'rotate-b
watch(isVerticalNavCollapsed, val => { watch(isVerticalNavCollapsed, val => {
verticalNavHeaderActionAnimationName.value = val ? 'rotate-180' : 'rotate-back-180' verticalNavHeaderActionAnimationName.value = val ? 'rotate-180' : 'rotate-back-180'
}) })
const dashboard = useDashboard()
dashboard.initial()
</script> </script>
<template> <template>
<VerticalNavLayout :nav-items="navItems"> <VerticalNavLayout :nav-items="dashboard.computeChainNav">
<!-- 👉 navbar --> <!-- 👉 navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }"> <template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center"> <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 { loadFonts } from "@/plugins/vuetify/webfontloader";
import "@/plugins/vuetify/@core/scss/template/index.scss"; import "@/plugins/vuetify/@core/scss/template/index.scss";
import "@/plugins/vuetify/styles/styles.scss"; import "@/plugins/vuetify/styles/styles.scss";
import { createApp } from "vue"; import { createApp, markRaw } from "vue";
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import router from "./router"; import { useDashboard } from "./stores/useDashboard";
// import router from "@/plugins/vuetify/router"; // import router from "@/plugins/vuetify/router";
import router from "./router";
loadFonts(); loadFonts();
@ -19,8 +20,9 @@ const app = createApp(App);
// Use plugins // Use plugins
app.use(vuetify); app.use(vuetify);
app.use(createPinia()); app.use(createPinia());
app.use(router);
app.use(layoutsPlugin); app.use(layoutsPlugin);
app.use(router);
// Mount vue app // Mount vue app
app.mount("#app"); app.mount("#app");

View File

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

View File

@ -1,25 +1,35 @@
<template> <script lang="ts" setup>
<div> import { useDashboard, LoadingStatus } from '@/stores/useDashboard';
<VCard import ChainSummary from '@/components/ChainSummary.vue';
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>
<VCard title="Want to integrate JWT? 🔒"> const dashboard = useDashboard()
<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> dashboard.$subscribe((mutation, state) => {
</VCard> 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> </div>
</template> </template>

View File

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

View File

@ -41,6 +41,8 @@ const resolveNavItemComponent = (item: NavLink | NavSectionTitle | NavGroup) =>
return VerticalNavLink return VerticalNavLink
} }
/* /*
Close overlay side when route is changed Close overlay side when route is changed
Close overlay vertical nav when link is clicked 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 isGroupActive = ref(false)
const isGroupOpen = ref(false) const isGroupOpen = ref(true)
/** /**
* Checks if any of children group is open or not. * Checks if any of children group is open or not.
@ -77,7 +77,9 @@ watch(() => route.path, () => {
const isActive = isNavGroupActive(props.item.children, router) 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 // 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 isGroupActive.value = isActive
}, { immediate: true }) }, { immediate: true })
@ -135,7 +137,7 @@ watch(openGroups, val => {
if (isAnyChildOpen(props.item.children)) if (isAnyChildOpen(props.item.children))
return return
isGroupOpen.value = isActive // isGroupOpen.value = isActive
isGroupActive.value = isActive isGroupActive.value = isActive
}, { deep: true }) }, { deep: true })

View File

@ -52,9 +52,10 @@ export const isNavLinkActive = (link: NavLink, router: Router) => {
if (!resolveRoutedName) if (!resolveRoutedName)
return false return false
return matchedRoutes.some(route => { return false
return route.name === resolveRoutedName || route.meta.navActiveLink === resolveRoutedName // 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 { setupLayouts } from "virtual:generated-layouts";
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import { useDashboard } from "@/stores/useDashboard";
import routes from "~pages"; import routes from "~pages";
// import { useDashboard } from "@/stores/useDashboard";
// const dashboard = useDashboard()
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [...setupLayouts(routes)], 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 // Docs: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
export default router; 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 { breakpointsVuetify } from '@vueuse/core'
import { VIcon } from 'vuetify/components' import { VAvatar } from 'vuetify/components'
// ❗ Logo SVG must be imported with ?raw suffix // ❗ Logo SVG must be imported with ?raw suffix
// import logo from '@/assets/logo.svg?raw' // import logo from '@/assets/logo.svg?raw'
@ -24,7 +24,7 @@ export const { themeConfig, layoutConfig } = defineThemeConfig({
isRtl: false, isRtl: false,
skin: Skins.Default, skin: Skins.Default,
routeTransition: RouteTransitions.Fade, routeTransition: RouteTransitions.Fade,
iconRenderer: VIcon, iconRenderer: VAvatar,
}, },
navbar: { navbar: {
type: NavbarType.Sticky, type: NavbarType.Sticky,

View File

@ -243,6 +243,13 @@
"@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-typescript" "^7.20.0" "@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": "@babel/standalone@^7.20.12":
version "7.20.15" version "7.20.15"
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.20.15.tgz#ef82f1a9789d21d8b23f74d9fa8acecbe6ced02c" 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" resolved "https://registry.yarnpkg.com/@casl/vue/-/vue-2.2.1.tgz#decb6966a982dabd41c26379692dfde2bf67793a"
integrity sha512-1OeGhT4A7VBkEACacF2ZlHkPiFJvyFy9h2PhBnMoetFDojMHbrn3ZjKgL5zQ4wSIrTQo9KbbzG3f0uAei2GKCQ== 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": "@esbuild/android-arm64@0.16.17":
version "0.16.17" version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
@ -1546,6 +1560,14 @@
dependencies: dependencies:
esquery "^1.0.1" 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": "@rollup/pluginutils@^5.0.2":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" 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" indent-string "^4.0.0"
strip-indent "^3.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: regexp.prototype.flags@^1.4.3:
version "1.4.3" version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"