feat: chart theme switch

This commit is contained in:
Alisa | Side.one 2023-05-28 09:19:39 +08:00
parent b10578e680
commit 2f93b4c10c
6 changed files with 230 additions and 237 deletions

View File

@ -1,50 +0,0 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { onMounted, ref } from 'vue';
const props = defineProps<{
themes: {
name: string;
icon: string;
}[];
}>();
const themeMap = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
const theme = ref(window.localStorage.getItem('theme') || 'dark');
const changeMode = () => {
let value = 'dark';
if (theme.value === 'dark') {
value = 'light';
}
if (
theme.value === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
value = 'dark';
}
if (value === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value);
};
onMounted(() => {
});
</script>
<template>
<div class="tooltip tooltip-bottom delay-1000">
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode">
<Icon :icon="props.themes[theme].icon" class="text-2xl" />
</button>
</div>
</template>

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
// }
},
}); });