feat: chart theme switch
This commit is contained in:
parent
b10578e680
commit
2f93b4c10c
@ -1,50 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Icon } from '@iconify/vue';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
themes: {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
}[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const themeMap = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
|
|
||||||
|
|
||||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
|
||||||
|
|
||||||
const changeMode = () => {
|
|
||||||
let value = 'dark';
|
|
||||||
if (theme.value === 'dark') {
|
|
||||||
value = 'light';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
theme.value === 'system' &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
) {
|
|
||||||
value = 'dark';
|
|
||||||
}
|
|
||||||
if (value === 'light') {
|
|
||||||
document.documentElement.classList.add('light');
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
document.documentElement.classList.remove('light');
|
|
||||||
}
|
|
||||||
document.documentElement.setAttribute('data-theme', value);
|
|
||||||
window.localStorage.setItem('theme', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="tooltip tooltip-bottom delay-1000">
|
|
||||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode">
|
|
||||||
<Icon :icon="props.themes[theme].icon" class="text-2xl" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,21 +1,30 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ApexCharts from 'vue3-apexcharts';
|
import ApexCharts from 'vue3-apexcharts';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useBaseStore } from '@/stores';
|
||||||
import { getDonutChartConfig } from './apexChartConfig';
|
import { getDonutChartConfig } from './apexChartConfig';
|
||||||
|
|
||||||
const props = defineProps(['series', 'labels']);
|
const props = defineProps(['series', 'labels']);
|
||||||
|
|
||||||
const expenseRationChartConfig = computed(() =>
|
const baseStore = useBaseStore();
|
||||||
getDonutChartConfig(window.localStorage.getItem('theme') || 'dark', props.labels)
|
|
||||||
);
|
const expenseRationChartConfig = computed(() => {
|
||||||
|
const theme = baseStore.theme;
|
||||||
|
getDonutChartConfig(theme, props.labels);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" />
|
<ApexCharts
|
||||||
|
type="donut"
|
||||||
|
height="410"
|
||||||
|
:options="expenseRationChartConfig"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'DonetChart',
|
name: 'DonetChart',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,40 +3,56 @@ import ApexCharts from 'vue3-apexcharts';
|
|||||||
import { getMarketPriceChartConfig } from './apexChartConfig';
|
import { getMarketPriceChartConfig } from './apexChartConfig';
|
||||||
import { useIndexModule } from '@/modules/[chain]/indexStore';
|
import { useIndexModule } from '@/modules/[chain]/indexStore';
|
||||||
import { computed, ref } from '@vue/reactivity';
|
import { computed, ref } from '@vue/reactivity';
|
||||||
|
import { useBaseStore } from '@/stores';
|
||||||
|
|
||||||
const store = useIndexModule();
|
const store = useIndexModule();
|
||||||
|
const baseStore = useBaseStore();
|
||||||
const chartConfig = computed(() => {
|
const chartConfig = computed(() => {
|
||||||
const labels = store.marketData.prices.map((item: any) => item[0]);
|
const theme = baseStore.theme;
|
||||||
return getMarketPriceChartConfig(window.localStorage.getItem('theme') || 'dark', labels);
|
const labels = store.marketData.prices.map((item: any) => item[0]);
|
||||||
|
return getMarketPriceChartConfig(theme, labels);
|
||||||
});
|
});
|
||||||
const kind = ref('price');
|
const kind = ref('price');
|
||||||
const series = computed(() => {
|
const series = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Price',
|
name: 'Price',
|
||||||
data:
|
data:
|
||||||
kind.value === 'price'
|
kind.value === 'price'
|
||||||
? store.marketData.prices.map((item: any) => item[1])
|
? store.marketData.prices.map((item: any) => item[1])
|
||||||
: store.marketData.total_volumes.map((item: any) => item[1]),
|
: store.marketData.total_volumes.map(
|
||||||
},
|
(item: any) => item[1]
|
||||||
];
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
function changeChart(type: string) {
|
function changeChart(type: string) {
|
||||||
kind.value = type;
|
kind.value = type;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tabs tabs-boxed bg-transparent justify-end">
|
<div class="tabs tabs-boxed bg-transparent justify-end">
|
||||||
<a class="tab text-xs mr-2 text-gray-400 uppercase" :class="{ 'tab-active': kind === 'price' }"
|
<a
|
||||||
@click="changeChart('price')">
|
class="tab text-xs mr-2 text-gray-400 uppercase"
|
||||||
Price
|
:class="{ 'tab-active': kind === 'price' }"
|
||||||
</a>
|
@click="changeChart('price')"
|
||||||
<a class="tab text-xs text-gray-400 uppercase" :class="{ 'tab-active': kind === 'volume' }"
|
>
|
||||||
@click="changeChart('volume')">
|
Price
|
||||||
Volume
|
</a>
|
||||||
</a>
|
<a
|
||||||
</div>
|
class="tab text-xs text-gray-400 uppercase"
|
||||||
<ApexCharts type="area" height="230" :options="chartConfig" :series="series" />
|
:class="{ 'tab-active': kind === 'volume' }"
|
||||||
|
@click="changeChart('volume')"
|
||||||
|
>
|
||||||
|
Volume
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ApexCharts
|
||||||
|
type="area"
|
||||||
|
height="230"
|
||||||
|
:options="chartConfig"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,56 +2,61 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
|
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
|
||||||
{
|
{
|
||||||
label: 'English',
|
label: 'English',
|
||||||
i18nLang: 'en',
|
i18nLang: 'en',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '中文',
|
label: '中文',
|
||||||
i18nLang: 'cn',
|
i18nLang: 'cn',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let locale = ref(useI18n({ useScope: 'global' }).locale);
|
let locale = ref(useI18n({ useScope: 'global' }).locale);
|
||||||
watch(locale, (val: string) => {
|
watch(locale, (val: string) => {
|
||||||
document.documentElement.setAttribute('lang', val as string);
|
document.documentElement.setAttribute('lang', val as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
||||||
|
|
||||||
watch(currentLang, (val: string) => {
|
watch(currentLang, (val: string) => {
|
||||||
document.documentElement.setAttribute('lang', val as string);
|
document.documentElement.setAttribute('lang', val as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLangChange = (lang: string) => {
|
const handleLangChange = (lang: string) => {
|
||||||
locale.value = lang;
|
locale.value = lang;
|
||||||
currentLang.value = lang;
|
currentLang.value = lang;
|
||||||
localStorage.setItem('lang', lang);
|
localStorage.setItem('lang', lang);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="dropdown"
|
class="dropdown"
|
||||||
:class="
|
:class="
|
||||||
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'
|
currentLang === 'ar'
|
||||||
"
|
? 'dropdown-right'
|
||||||
>
|
: 'dropdown-bottom dropdown-end'
|
||||||
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
"
|
||||||
<Icon icon="mdi-translate" class="text-2xl" />
|
|
||||||
</label>
|
|
||||||
<ul
|
|
||||||
tabindex="0"
|
|
||||||
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
|
||||||
>
|
>
|
||||||
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
|
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
||||||
<a
|
<Icon
|
||||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
icon="mdi-translate"
|
||||||
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
class="text-2xl text-gray-500 dark:text-gray-400"
|
||||||
@click="handleLangChange(lang.i18nLang)"
|
/>
|
||||||
>{{ lang.label }}</a
|
</label>
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
||||||
>
|
>
|
||||||
</li>
|
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
|
||||||
</ul>
|
<a
|
||||||
</div>
|
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||||
|
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
||||||
|
@click="handleLangChange(lang.i18nLang)"
|
||||||
|
>{{ lang.label }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,45 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, computed } from 'vue';
|
||||||
|
import { useBaseStore } from '@/stores';
|
||||||
|
|
||||||
const themeMap: Record<string, string> = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
|
const themeMap: Record<string, string> = {
|
||||||
|
system: 'mdi-laptop',
|
||||||
|
light: 'mdi-weather-sunny',
|
||||||
|
dark: 'mdi-weather-night',
|
||||||
|
};
|
||||||
|
const baseStore = useBaseStore();
|
||||||
|
const theme = computed(() => {
|
||||||
|
return baseStore.theme;
|
||||||
|
});
|
||||||
|
|
||||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
const changeMode = (val?: 'dark' | 'light') => {
|
||||||
|
let value: 'dark' | 'light' = 'dark';
|
||||||
const changeMode = (val?: string) => {
|
const currentValue: 'dark' | 'light' = val || theme.value;
|
||||||
let value = 'dark';
|
if (currentValue === 'dark') {
|
||||||
const currentValue = val || theme.value;
|
value = 'light';
|
||||||
if (currentValue === 'dark') {
|
}
|
||||||
value = 'light';
|
if (value === 'light') {
|
||||||
}
|
document.documentElement.classList.add('light');
|
||||||
if (
|
document.documentElement.classList.remove('dark');
|
||||||
currentValue === 'system' &&
|
} else {
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
document.documentElement.classList.add('dark');
|
||||||
) {
|
document.documentElement.classList.remove('light');
|
||||||
value = 'dark';
|
}
|
||||||
}
|
document.documentElement.setAttribute('data-theme', value);
|
||||||
if (value === 'light') {
|
window.localStorage.setItem('theme', value);
|
||||||
document.documentElement.classList.add('light');
|
baseStore.theme = value;
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
document.documentElement.classList.remove('light');
|
|
||||||
}
|
|
||||||
document.documentElement.setAttribute('data-theme', value);
|
|
||||||
window.localStorage.setItem('theme', value);
|
|
||||||
theme.value = value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tooltip tooltip-bottom delay-1000">
|
<div class="tooltip tooltip-bottom delay-1000">
|
||||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
|
<button
|
||||||
<Icon :icon="themeMap?.[theme]" class="text-2xl" />
|
class="btn btn-ghost btn-circle btn-sm mx-1"
|
||||||
</button>
|
@click="changeMode()"
|
||||||
</div>
|
>
|
||||||
|
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -7,101 +7,111 @@ import { hashTx } from '@/libs';
|
|||||||
import { fromBase64 } from '@cosmjs/encoding';
|
import { fromBase64 } from '@cosmjs/encoding';
|
||||||
|
|
||||||
export const useBaseStore = defineStore('baseStore', {
|
export const useBaseStore = defineStore('baseStore', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
earlest: {} as Block,
|
earlest: {} as Block,
|
||||||
latest: {} as Block,
|
latest: {} as Block,
|
||||||
recents: [] as Block[],
|
recents: [] as Block[],
|
||||||
};
|
theme: (window.localStorage.getItem('theme') || 'dark') as
|
||||||
},
|
| 'light'
|
||||||
getters: {
|
| 'dark',
|
||||||
blocktime(): number {
|
};
|
||||||
if (this.earlest && this.latest) {
|
|
||||||
if (
|
|
||||||
this.latest.block?.header?.height !==
|
|
||||||
this.earlest.block?.header?.height
|
|
||||||
) {
|
|
||||||
const diff = dayjs(this.latest.block?.header?.time).diff(
|
|
||||||
this.earlest.block?.header?.time
|
|
||||||
);
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 6000;
|
|
||||||
},
|
},
|
||||||
blockchain() {
|
getters: {
|
||||||
return useBlockchain();
|
blocktime(): number {
|
||||||
},
|
if (this.earlest && this.latest) {
|
||||||
currentChainId(): string {
|
if (
|
||||||
return this.latest.block?.header.chain_id || ""
|
this.latest.block?.header?.height !==
|
||||||
},
|
this.earlest.block?.header?.height
|
||||||
txsInRecents() {
|
) {
|
||||||
const txs = [] as { height: string; hash: string; tx: DecodedTxRaw }[];
|
const diff = dayjs(this.latest.block?.header?.time).diff(
|
||||||
this.recents.forEach((b) =>
|
this.earlest.block?.header?.time
|
||||||
b.block?.data?.txs.forEach((tx: string) => {
|
);
|
||||||
if (tx) {
|
return diff;
|
||||||
const raw = fromBase64(tx);
|
}
|
||||||
try {
|
|
||||||
txs.push({
|
|
||||||
height: b.block.header.height,
|
|
||||||
hash: hashTx(raw),
|
|
||||||
tx: decodeTxRaw(raw),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
return 6000;
|
||||||
})
|
},
|
||||||
);
|
blockchain() {
|
||||||
return txs;
|
return useBlockchain();
|
||||||
},
|
},
|
||||||
},
|
currentChainId(): string {
|
||||||
actions: {
|
return this.latest.block?.header.chain_id || '';
|
||||||
async initial() {
|
},
|
||||||
this.fetchLatest();
|
txsInRecents() {
|
||||||
},
|
const txs = [] as {
|
||||||
async clearRecentBlocks() {
|
height: string;
|
||||||
this.recents = [];
|
hash: string;
|
||||||
},
|
tx: DecodedTxRaw;
|
||||||
async fetchLatest() {
|
}[];
|
||||||
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
this.recents.forEach((b) =>
|
||||||
if (
|
b.block?.data?.txs.forEach((tx: string) => {
|
||||||
!this.earlest ||
|
if (tx) {
|
||||||
this.earlest?.block?.header?.chain_id !=
|
const raw = fromBase64(tx);
|
||||||
this.latest?.block?.header?.chain_id
|
try {
|
||||||
) {
|
txs.push({
|
||||||
//reset earlest and recents
|
height: b.block.header.height,
|
||||||
this.earlest = this.latest;
|
hash: hashTx(raw),
|
||||||
this.recents = [];
|
tx: decodeTxRaw(raw),
|
||||||
}
|
});
|
||||||
//check if the block exists in recents
|
} catch (e) {
|
||||||
if (
|
console.error(e);
|
||||||
this.recents.findIndex(
|
}
|
||||||
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
|
}
|
||||||
) === -1
|
})
|
||||||
) {
|
);
|
||||||
if (this.recents.length >= 50) {
|
return txs;
|
||||||
this.recents.shift();
|
},
|
||||||
}
|
|
||||||
this.recents.push(this.latest);
|
|
||||||
}
|
|
||||||
return this.latest;
|
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
async initial() {
|
||||||
|
this.fetchLatest();
|
||||||
|
},
|
||||||
|
async clearRecentBlocks() {
|
||||||
|
this.recents = [];
|
||||||
|
},
|
||||||
|
async fetchLatest() {
|
||||||
|
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
||||||
|
if (
|
||||||
|
!this.earlest ||
|
||||||
|
this.earlest?.block?.header?.chain_id !=
|
||||||
|
this.latest?.block?.header?.chain_id
|
||||||
|
) {
|
||||||
|
//reset earlest and recents
|
||||||
|
this.earlest = this.latest;
|
||||||
|
this.recents = [];
|
||||||
|
}
|
||||||
|
//check if the block exists in recents
|
||||||
|
if (
|
||||||
|
this.recents.findIndex(
|
||||||
|
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
|
||||||
|
) === -1
|
||||||
|
) {
|
||||||
|
if (this.recents.length >= 50) {
|
||||||
|
this.recents.shift();
|
||||||
|
}
|
||||||
|
this.recents.push(this.latest);
|
||||||
|
}
|
||||||
|
return this.latest;
|
||||||
|
},
|
||||||
|
|
||||||
async fetchValidatorByHeight(height?: number, offset = 0) {
|
async fetchValidatorByHeight(height?: number, offset = 0) {
|
||||||
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset);
|
return this.blockchain.rpc.getBaseValidatorsetAt(
|
||||||
|
String(height),
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async fetchLatestValidators(offset = 0) {
|
||||||
|
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
|
||||||
|
},
|
||||||
|
async fetchBlock(height?: number | string) {
|
||||||
|
return this.blockchain.rpc.getBaseBlockAt(String(height));
|
||||||
|
},
|
||||||
|
async fetchAbciInfo() {
|
||||||
|
return this.blockchain.rpc.getBaseNodeInfo();
|
||||||
|
},
|
||||||
|
// async fetchNodeInfo() {
|
||||||
|
// return this.blockchain.rpc.no()
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
async fetchLatestValidators(offset = 0) {
|
|
||||||
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
|
|
||||||
},
|
|
||||||
async fetchBlock(height?: number | string) {
|
|
||||||
return this.blockchain.rpc.getBaseBlockAt(String(height));
|
|
||||||
},
|
|
||||||
async fetchAbciInfo() {
|
|
||||||
return this.blockchain.rpc.getBaseNodeInfo();
|
|
||||||
},
|
|
||||||
// async fetchNodeInfo() {
|
|
||||||
// return this.blockchain.rpc.no()
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user