diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a07fb5ff91..eb30eed53a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -916,6 +916,7 @@ caption: "キャプション" loggedInAsBot: "Botアカウントでログイン中" tools: "ツール" cannotLoad: "読み込めません" +numberOfProfileView: "プロフィール表示回数" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index d99a5478e9..9ca7deaf80 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -14,26 +14,9 @@ As this is part of Chart.js's API it makes sense to disable the check here. */ import { onMounted, ref, watch, PropType, onUnmounted } from 'vue'; -import { - Chart, - ArcElement, - LineElement, - BarElement, - PointElement, - BarController, - LineController, - CategoryScale, - LinearScale, - TimeScale, - Legend, - Title, - Tooltip, - SubTitle, - Filler, -} from 'chart.js'; +import { Chart } from 'chart.js'; import 'chartjs-adapter-date-fns'; import { enUS } from 'date-fns/locale'; -import zoomPlugin from 'chartjs-plugin-zoom'; import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; import { defaultStore } from '@/store'; @@ -41,6 +24,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; import { alpha } from '@/scripts/color'; import date from '@/filters/date'; +import { initChart } from '@/scripts/init-chart'; + +initChart(); const props = defineProps({ src: { @@ -82,25 +68,6 @@ const props = defineProps({ }, }); -Chart.register( - ArcElement, - LineElement, - BarElement, - PointElement, - BarController, - LineController, - CategoryScale, - LinearScale, - TimeScale, - Legend, - Title, - Tooltip, - SubTitle, - Filler, - zoomPlugin, - gradient, -); - const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); const negate = arr => arr.map(x => -x); @@ -742,6 +709,33 @@ const fetchPerUserNotesChart = async (): Promise => { }; }; +const fetchPerUserPvChart = async (): Promise => { + const raw = await os.apiGet('charts/user/pv', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Unique PV (user)', + type: 'area', + data: format(raw.upv.user), + color: colors.purple, + }, { + name: 'PV (user)', + type: 'area', + data: format(raw.pv.user), + color: colors.green, + }, { + name: 'Unique PV (visitor)', + type: 'area', + data: format(raw.upv.visitor), + color: colors.yellow, + }, { + name: 'PV (visitor)', + type: 'area', + data: format(raw.pv.visitor), + color: colors.blue, + }], + }; +}; + const fetchPerUserFollowingChart = async (): Promise => { const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); return { @@ -814,6 +808,7 @@ const fetchAndRender = async () => { case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); case 'per-user-notes': return fetchPerUserNotesChart(); + case 'per-user-pv': return fetchPerUserPvChart(); case 'per-user-following': return fetchPerUserFollowingChart(); case 'per-user-followers': return fetchPerUserFollowersChart(); case 'per-user-drive': return fetchPerUserDriveChart(); diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index 078d0721da..5350928bfe 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -9,23 +9,7 @@ diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue new file mode 100644 index 0000000000..d709bc01b9 --- /dev/null +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue new file mode 100644 index 0000000000..f9dce3a9e8 --- /dev/null +++ b/packages/frontend/src/pages/user/activity.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue index 523072d2e6..0cc1524663 100644 --- a/packages/frontend/src/pages/user/index.activity.vue +++ b/packages/frontend/src/pages/user/index.activity.vue @@ -33,10 +33,16 @@ let chartSrc = $ref('per-user-notes'); function showMenu(ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.notes, - active: true, + active: chartSrc === 'per-user-notes', action: () => { chartSrc = 'per-user-notes'; }, + }, { + text: i18n.ts.numberOfProfileView, + active: chartSrc === 'per-user-pv', + action: () => { + chartSrc = 'per-user-pv'; + }, }, /*, { text: i18n.ts.following, action: () => { diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index f40cd0b8d6..b60cef3729 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -5,6 +5,7 @@
+ @@ -32,6 +33,7 @@ import { i18n } from '@/i18n'; import { $i } from '@/account'; const XHome = defineAsyncComponent(() => import('./home.vue')); +const XActivity = defineAsyncComponent(() => import('./activity.vue')); const XReactions = defineAsyncComponent(() => import('./reactions.vue')); const XClips = defineAsyncComponent(() => import('./clips.vue')); const XPages = defineAsyncComponent(() => import('./pages.vue')); @@ -70,6 +72,10 @@ const headerTabs = $computed(() => user ? [{ key: 'home', title: i18n.ts.overview, icon: 'ti ti-home', +}, { + key: 'activity', + title: i18n.ts.activity, + icon: 'ti ti-chart-line', }, ...($i && ($i.id === user.id)) || user.publicReactions ? [{ key: 'reactions', title: i18n.ts.reaction, diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts new file mode 100644 index 0000000000..32f887f2e7 --- /dev/null +++ b/packages/frontend/src/scripts/init-chart.ts @@ -0,0 +1,44 @@ +import { + Chart, + ArcElement, + LineElement, + BarElement, + PointElement, + BarController, + LineController, + DoughnutController, + CategoryScale, + LinearScale, + TimeScale, + Legend, + Title, + Tooltip, + SubTitle, + Filler, +} from 'chart.js'; +import gradient from 'chartjs-plugin-gradient'; +import zoomPlugin from 'chartjs-plugin-zoom'; +import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; + +export function initChart() { + Chart.register( + ArcElement, + LineElement, + BarElement, + PointElement, + BarController, + LineController, + DoughnutController, + CategoryScale, + LinearScale, + TimeScale, + Legend, + Title, + Tooltip, + SubTitle, + Filler, + MatrixController, MatrixElement, + zoomPlugin, + gradient, + ); +}