This commit is contained in:
syuilo 2023-04-22 12:50:40 +09:00
parent eacdc0136f
commit d437e148db
3 changed files with 174 additions and 14 deletions

View File

@ -0,0 +1,157 @@
<template>
<div>
<MkLoading v-if="fetching"/>
<div v-show="!fetching" :class="$style.root">
<canvas ref="chartEl"></canvas>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import tinycolor from 'tinycolor2';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { useChartTooltip } from '@/scripts/use-chart-tooltip';
import { chartVLine } from '@/scripts/chart-vline';
import { initChart } from '@/scripts/init-chart';
initChart();
const chartEl = $shallowRef<HTMLCanvasElement>(null);
const now = new Date();
let chartInstance: Chart = null;
const chartLimit = 30;
let fetching = $ref(true);
const { handler: externalTooltipHandler } = useChartTooltip();
async function renderChart() {
if (chartInstance) {
chartInstance.destroy();
}
const getDate = (ago: number) => {
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
return new Date(y, m, d - ago);
};
const format = (arr) => {
return arr.map((v, i) => ({
x: getDate(i).getTime(),
y: v,
}));
};
const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const computedStyle = getComputedStyle(document.documentElement);
const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
const colorRead = accent;
const colorWrite = '#2ecc71';
const max = Math.max(...raw.read);
chartInstance = new Chart(chartEl, {
type: 'bar',
data: {
datasets: [{
parsing: false,
label: 'Read',
data: format(raw.read).slice().reverse(),
pointRadius: 0,
borderWidth: 0,
borderJoinStyle: 'round',
borderRadius: 4,
backgroundColor: colorRead,
barPercentage: 0.5,
categoryPercentage: 1,
fill: true,
}],
},
options: {
aspectRatio: 2.5,
layout: {
padding: {
left: 0,
right: 8,
top: 0,
bottom: 0,
},
},
scales: {
x: {
type: 'time',
offset: true,
time: {
stepSize: 1,
unit: 'day',
displayFormats: {
day: 'M/d',
month: 'Y/M',
},
},
grid: {
display: false,
},
ticks: {
display: true,
maxRotation: 0,
autoSkipPadding: 8,
},
},
y: {
position: 'left',
suggestedMax: 10,
grid: {
display: true,
},
ticks: {
display: true,
//mirror: true,
},
},
},
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
mode: 'index',
animation: {
duration: 0,
},
external: externalTooltipHandler,
},
gradient,
},
},
plugins: [chartVLine(vLineColor)],
});
fetching = false;
}
onMounted(async () => {
renderChart();
});
</script>
<style lang="scss" module>
.root {
padding: 20px;
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-if="meta" :class="$style.root"> <div v-if="meta" :class="$style.root">
<div :class="$style.main"> <div :class="[$style.main, $style.panel]">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.mainIcon"/> <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.mainIcon"/>
<button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button> <button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
<div :class="$style.mainFg"> <div :class="$style.mainFg">
@ -24,21 +24,24 @@
</div> </div>
</div> </div>
<div v-if="stats" :class="$style.stats"> <div v-if="stats" :class="$style.stats">
<div :class="$style.statsItem"> <div :class="[$style.statsItem, $style.panel]">
<div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div> <div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div>
<div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div> <div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div>
</div> </div>
<div :class="$style.statsItem"> <div :class="[$style.statsItem, $style.panel]">
<div :class="$style.statsItemLabel">{{ i18n.ts.notes }}</div> <div :class="$style.statsItemLabel">{{ i18n.ts.notes }}</div>
<div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div> <div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div>
</div> </div>
</div> </div>
<div v-if="instance.policies.ltlAvailable" :class="$style.tl"> <div v-if="instance.policies.ltlAvailable" :class="[$style.tl, $style.panel]">
<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div> <div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
<div :class="$style.tlBody"> <div :class="$style.tlBody">
<MkTimeline src="local"/> <MkTimeline src="local"/>
</div> </div>
</div> </div>
<div :class="[$style.activeUsersChart, $style.panel]">
<XActiveUsersChart/>
</div>
</div> </div>
</template> </template>
@ -57,6 +60,7 @@ import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import number from '@/filters/number'; import number from '@/filters/number';
import MkNumber from '@/components/MkNumber.vue'; import MkNumber from '@/components/MkNumber.vue';
import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';
let meta = $ref<Instance>(); let meta = $ref<Instance>();
let stats = $ref(null); let stats = $ref(null);
@ -119,11 +123,14 @@ function exploreOtherServers() {
padding: 32px 0 0 0; padding: 32px 0 0 0;
} }
.main { .panel {
position: relative; position: relative;
background: var(--panel); background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: 0 12px 32px rgb(0 0 0 / 25%); box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
}
.main {
text-align: center; text-align: center;
} }
@ -185,11 +192,7 @@ function exploreOtherServers() {
} }
.statsItem { .statsItem {
position: relative;
background: var(--panel);
border-radius: var(--radius);
overflow: clip; overflow: clip;
box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
padding: 16px 20px; padding: 16px 20px;
} }
@ -205,11 +208,7 @@ function exploreOtherServers() {
} }
.tl { .tl {
position: relative;
background: var(--panel);
border-radius: var(--radius);
overflow: clip; overflow: clip;
box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
} }
.tlHeader { .tlHeader {
@ -221,4 +220,8 @@ function exploreOtherServers() {
height: 350px; height: 350px;
overflow: auto; overflow: auto;
} }
.activeUsersChart {
}
</style> </style>

View File

@ -10,7 +10,7 @@
</div> </div>
<div class="main"> <div class="main">
<div class="header"> <div v-if="!root" class="header">
<div v-if="narrow === false" class="wide"> <div v-if="narrow === false" class="wide">
<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA> <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA> <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA>