enhance(client): user activity page
This commit is contained in:
		
							parent
							
								
									1df23a839a
								
							
						
					
					
						commit
						7a95339296
					
				
					 17 changed files with 564 additions and 339 deletions
				
			
		|  | @ -916,6 +916,7 @@ caption: "キャプション" | ||||||
| loggedInAsBot: "Botアカウントでログイン中" | loggedInAsBot: "Botアカウントでログイン中" | ||||||
| tools: "ツール" | tools: "ツール" | ||||||
| cannotLoad: "読み込めません" | cannotLoad: "読み込めません" | ||||||
|  | numberOfProfileView: "プロフィール表示回数" | ||||||
| 
 | 
 | ||||||
| _sensitiveMediaDetection: | _sensitiveMediaDetection: | ||||||
|   description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" |   description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" | ||||||
|  |  | ||||||
|  | @ -14,26 +14,9 @@ | ||||||
|   As this is part of Chart.js's API it makes sense to disable the check here. |   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 { onMounted, ref, watch, PropType, onUnmounted } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import 'chartjs-adapter-date-fns'; | import 'chartjs-adapter-date-fns'; | ||||||
| import { enUS } from 'date-fns/locale'; | import { enUS } from 'date-fns/locale'; | ||||||
| import zoomPlugin from 'chartjs-plugin-zoom'; |  | ||||||
| import gradient from 'chartjs-plugin-gradient'; | import gradient from 'chartjs-plugin-gradient'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
|  | @ -41,6 +24,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
| import date from '@/filters/date'; | import date from '@/filters/date'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
|  | 
 | ||||||
|  | initChart(); | ||||||
| 
 | 
 | ||||||
| const props = defineProps({ | const props = defineProps({ | ||||||
| 	src: { | 	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 sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); | ||||||
| const negate = arr => arr.map(x => -x); | const negate = arr => arr.map(x => -x); | ||||||
| 
 | 
 | ||||||
|  | @ -742,6 +709,33 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const fetchPerUserPvChart = async (): Promise<typeof chartData> => { | ||||||
|  | 	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<typeof chartData> => { | const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
|  | @ -814,6 +808,7 @@ const fetchAndRender = async () => { | ||||||
| 			case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); | 			case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); | ||||||
| 
 | 
 | ||||||
| 			case 'per-user-notes': return fetchPerUserNotesChart(); | 			case 'per-user-notes': return fetchPerUserNotesChart(); | ||||||
|  | 			case 'per-user-pv': return fetchPerUserPvChart(); | ||||||
| 			case 'per-user-following': return fetchPerUserFollowingChart(); | 			case 'per-user-following': return fetchPerUserFollowingChart(); | ||||||
| 			case 'per-user-followers': return fetchPerUserFollowersChart(); | 			case 'per-user-followers': return fetchPerUserFollowersChart(); | ||||||
| 			case 'per-user-drive': return fetchPerUserDriveChart(); | 			case 'per-user-drive': return fetchPerUserDriveChart(); | ||||||
|  |  | ||||||
|  | @ -9,23 +9,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; | import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import { enUS } from 'date-fns/locale'; | import { enUS } from 'date-fns/locale'; | ||||||
| import tinycolor from 'tinycolor2'; | import tinycolor from 'tinycolor2'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
|  | @ -35,24 +19,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; | import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	MatrixController, MatrixElement, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	src: string; | 	src: string; | ||||||
|  |  | ||||||
|  | @ -77,24 +77,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted } from 'vue'; | import { onMounted } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	DoughnutController, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import MkSelect from '@/components/form/select.vue'; | import MkSelect from '@/components/form/select.vue'; | ||||||
| import MkChart from '@/components/MkChart.vue'; | import MkChart from '@/components/MkChart.vue'; | ||||||
| import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
|  | @ -103,24 +86,9 @@ import { i18n } from '@/i18n'; | ||||||
| import MkHeatmap from '@/components/MkHeatmap.vue'; | import MkHeatmap from '@/components/MkHeatmap.vue'; | ||||||
| import MkFolder from '@/components/MkFolder.vue'; | import MkFolder from '@/components/MkFolder.vue'; | ||||||
| import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; | import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	DoughnutController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const chartLimit = 500; | const chartLimit = 500; | ||||||
| let chartSpan = $ref<'hour' | 'day'>('hour'); | let chartSpan = $ref<'hour' | 'day'>('hour'); | ||||||
|  |  | ||||||
|  | @ -9,23 +9,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; | import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import { enUS } from 'date-fns/locale'; | import { enUS } from 'date-fns/locale'; | ||||||
| import tinycolor from 'tinycolor2'; | import tinycolor from 'tinycolor2'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
|  | @ -35,24 +19,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; | import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	MatrixController, MatrixElement, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const rootEl = $ref<HTMLDivElement>(null); | const rootEl = $ref<HTMLDivElement>(null); | ||||||
| const chartEl = $ref<HTMLCanvasElement>(null); | const chartEl = $ref<HTMLCanvasElement>(null); | ||||||
|  |  | ||||||
|  | @ -52,21 +52,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, markRaw } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import MkwFederation from '../../widgets/federation.vue'; | import MkwFederation from '../../widgets/federation.vue'; | ||||||
| import MkButton from '@/components/MkButton.vue'; | import MkButton from '@/components/MkButton.vue'; | ||||||
| import MkSelect from '@/components/form/select.vue'; | import MkSelect from '@/components/form/select.vue'; | ||||||
|  | @ -79,21 +65,9 @@ import number from '@/filters/number'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { stream } from '@/stream'; | import { stream } from '@/stream'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
|  |  | ||||||
|  | @ -9,23 +9,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; | import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import { enUS } from 'date-fns/locale'; | import { enUS } from 'date-fns/locale'; | ||||||
| import tinycolor from 'tinycolor2'; | import tinycolor from 'tinycolor2'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
|  | @ -35,24 +19,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import gradient from 'chartjs-plugin-gradient'; | import gradient from 'chartjs-plugin-gradient'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	gradient, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const chartEl = $ref<HTMLCanvasElement>(null); | const chartEl = $ref<HTMLCanvasElement>(null); | ||||||
| const now = new Date(); | const now = new Date(); | ||||||
|  |  | ||||||
|  | @ -16,23 +16,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, ref } from 'vue'; | import { onMounted, onUnmounted, ref } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import gradient from 'chartjs-plugin-gradient'; | import gradient from 'chartjs-plugin-gradient'; | ||||||
| import { enUS } from 'date-fns/locale'; | import { enUS } from 'date-fns/locale'; | ||||||
| import tinycolor from 'tinycolor2'; | import tinycolor from 'tinycolor2'; | ||||||
|  | @ -45,24 +29,9 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	gradient, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const chartLimit = 50; | const chartLimit = 50; | ||||||
| const chartEl = $ref<HTMLCanvasElement>(); | const chartEl = $ref<HTMLCanvasElement>(); | ||||||
|  |  | ||||||
|  | @ -4,45 +4,13 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, ref } from 'vue'; | import { onMounted, onUnmounted, ref } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| 	DoughnutController, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import number from '@/filters/number'; | import number from '@/filters/number'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
| import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	DoughnutController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	data: { name: string; value: number; color: string; onClick?: () => void }[]; | 	data: { name: string; value: number; color: string; onClick?: () => void }[]; | ||||||
|  |  | ||||||
|  | @ -4,46 +4,16 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { watch, onMounted, onUnmounted, ref } from 'vue'; | import { watch, onMounted, onUnmounted, ref } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import number from '@/filters/number'; | import number from '@/filters/number'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
| import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	type: string; | 	type: string; | ||||||
|  |  | ||||||
|  | @ -4,46 +4,16 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { watch, onMounted, onUnmounted, ref } from 'vue'; | import { watch, onMounted, onUnmounted, ref } from 'vue'; | ||||||
| import { | import { Chart } from 'chart.js'; | ||||||
| 	Chart, |  | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| } from 'chart.js'; |  | ||||||
| import number from '@/filters/number'; | import number from '@/filters/number'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
| import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
| import { chartVLine } from '@/scripts/chart-vline'; | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
| import { alpha } from '@/scripts/color'; | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
| 
 | 
 | ||||||
| Chart.register( | initChart(); | ||||||
| 	ArcElement, |  | ||||||
| 	LineElement, |  | ||||||
| 	BarElement, |  | ||||||
| 	PointElement, |  | ||||||
| 	BarController, |  | ||||||
| 	LineController, |  | ||||||
| 	CategoryScale, |  | ||||||
| 	LinearScale, |  | ||||||
| 	TimeScale, |  | ||||||
| 	Legend, |  | ||||||
| 	Title, |  | ||||||
| 	Tooltip, |  | ||||||
| 	SubTitle, |  | ||||||
| 	Filler, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	type: string; | 	type: string; | ||||||
|  |  | ||||||
							
								
								
									
										217
									
								
								packages/frontend/src/pages/user/activity.heatmap.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								packages/frontend/src/pages/user/activity.heatmap.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,217 @@ | ||||||
|  | <template> | ||||||
|  | <div ref="rootEl"> | ||||||
|  | 	<MkLoading v-if="fetching"/> | ||||||
|  | 	<div v-else> | ||||||
|  | 		<canvas ref="chartEl"></canvas> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; | ||||||
|  | import { Chart } from 'chart.js'; | ||||||
|  | import { enUS } from 'date-fns/locale'; | ||||||
|  | import tinycolor from 'tinycolor2'; | ||||||
|  | import * as misskey from 'misskey-js'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import 'chartjs-adapter-date-fns'; | ||||||
|  | import { defaultStore } from '@/store'; | ||||||
|  | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
|  | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
|  | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
|  | 
 | ||||||
|  | initChart(); | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	src: string; | ||||||
|  | 	user: misskey.entities.User; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const rootEl = $ref<HTMLDivElement>(null); | ||||||
|  | const chartEl = $ref<HTMLCanvasElement>(null); | ||||||
|  | const now = new Date(); | ||||||
|  | let chartInstance: Chart = null; | ||||||
|  | let fetching = $ref(true); | ||||||
|  | 
 | ||||||
|  | const { handler: externalTooltipHandler } = useChartTooltip({ | ||||||
|  | 	position: 'middle', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | async function renderChart() { | ||||||
|  | 	if (chartInstance) { | ||||||
|  | 		chartInstance.destroy(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const wide = rootEl.offsetWidth > 700; | ||||||
|  | 	const narrow = rootEl.offsetWidth < 400; | ||||||
|  | 
 | ||||||
|  | 	const weeks = wide ? 50 : narrow ? 10 : 25; | ||||||
|  | 	const chartLimit = 7 * weeks; | ||||||
|  | 
 | ||||||
|  | 	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) => { | ||||||
|  | 			const dt = getDate(i); | ||||||
|  | 			const iso = `${dt.getFullYear()}-${(dt.getMonth() + 1).toString().padStart(2, '0')}-${dt.getDate().toString().padStart(2, '0')}`; | ||||||
|  | 			return { | ||||||
|  | 				x: iso, | ||||||
|  | 				y: dt.getDay(), | ||||||
|  | 				d: iso, | ||||||
|  | 				v, | ||||||
|  | 			}; | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	let values; | ||||||
|  | 
 | ||||||
|  | 	if (props.src === 'notes') { | ||||||
|  | 		const raw = await os.api('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' }); | ||||||
|  | 		values = raw.inc; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fetching = false; | ||||||
|  | 
 | ||||||
|  | 	await nextTick(); | ||||||
|  | 
 | ||||||
|  | 	const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; | ||||||
|  | 
 | ||||||
|  | 	// フォントカラー | ||||||
|  | 	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); | ||||||
|  | 
 | ||||||
|  | 	const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300'; | ||||||
|  | 
 | ||||||
|  | 	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする | ||||||
|  | 	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; | ||||||
|  | 
 | ||||||
|  | 	const min = Math.max(0, Math.min(...values) - 1); | ||||||
|  | 
 | ||||||
|  | 	const marginEachCell = 4; | ||||||
|  | 
 | ||||||
|  | 	chartInstance = new Chart(chartEl, { | ||||||
|  | 		type: 'matrix', | ||||||
|  | 		data: { | ||||||
|  | 			datasets: [{ | ||||||
|  | 				label: '', | ||||||
|  | 				data: format(values), | ||||||
|  | 				pointRadius: 0, | ||||||
|  | 				borderWidth: 0, | ||||||
|  | 				borderJoinStyle: 'round', | ||||||
|  | 				borderRadius: 3, | ||||||
|  | 				backgroundColor(c) { | ||||||
|  | 					const value = c.dataset.data[c.dataIndex].v; | ||||||
|  | 					let a = (value - min) / max; | ||||||
|  | 					if (value !== 0) { // 0でない限りは完全に不可視にはしない | ||||||
|  | 						a = Math.max(a, 0.05); | ||||||
|  | 					} | ||||||
|  | 					return alpha(color, a); | ||||||
|  | 				}, | ||||||
|  | 				fill: true, | ||||||
|  | 				width(c) { | ||||||
|  | 					const a = c.chart.chartArea ?? {}; | ||||||
|  | 					return (a.right - a.left) / weeks - marginEachCell; | ||||||
|  | 				}, | ||||||
|  | 				height(c) { | ||||||
|  | 					const a = c.chart.chartArea ?? {}; | ||||||
|  | 					return (a.bottom - a.top) / 7 - marginEachCell; | ||||||
|  | 				}, | ||||||
|  | 			}], | ||||||
|  | 		}, | ||||||
|  | 		options: { | ||||||
|  | 			aspectRatio: wide ? 6 : narrow ? 1.8 : 3.2, | ||||||
|  | 			layout: { | ||||||
|  | 				padding: { | ||||||
|  | 					left: 8, | ||||||
|  | 					right: 0, | ||||||
|  | 					top: 0, | ||||||
|  | 					bottom: 0, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			scales: { | ||||||
|  | 				x: { | ||||||
|  | 					type: 'time', | ||||||
|  | 					offset: true, | ||||||
|  | 					position: 'bottom', | ||||||
|  | 					time: { | ||||||
|  | 						unit: 'week', | ||||||
|  | 						round: 'week', | ||||||
|  | 						isoWeekday: 0, | ||||||
|  | 						displayFormats: { | ||||||
|  | 							week: 'MMM dd', | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					grid: { | ||||||
|  | 						display: false, | ||||||
|  | 						color: gridColor, | ||||||
|  | 						borderColor: 'rgb(0, 0, 0, 0)', | ||||||
|  | 					}, | ||||||
|  | 					ticks: { | ||||||
|  | 						display: true, | ||||||
|  | 						maxRotation: 0, | ||||||
|  | 						autoSkipPadding: 8, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				y: { | ||||||
|  | 					offset: true, | ||||||
|  | 					reverse: true, | ||||||
|  | 					position: 'right', | ||||||
|  | 					grid: { | ||||||
|  | 						display: false, | ||||||
|  | 						color: gridColor, | ||||||
|  | 						borderColor: 'rgb(0, 0, 0, 0)', | ||||||
|  | 					}, | ||||||
|  | 					ticks: { | ||||||
|  | 						maxRotation: 0, | ||||||
|  | 						autoSkip: true, | ||||||
|  | 						padding: 1, | ||||||
|  | 						font: { | ||||||
|  | 							size: 9, | ||||||
|  | 						}, | ||||||
|  | 						callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value], | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			animation: false, | ||||||
|  | 			plugins: { | ||||||
|  | 				legend: { | ||||||
|  | 					display: false, | ||||||
|  | 				}, | ||||||
|  | 				tooltip: { | ||||||
|  | 					enabled: false, | ||||||
|  | 					callbacks: { | ||||||
|  | 						title(context) { | ||||||
|  | 							const v = context[0].dataset.data[context[0].dataIndex]; | ||||||
|  | 							return v.d; | ||||||
|  | 						}, | ||||||
|  | 						label(context) { | ||||||
|  | 							const v = context.dataset.data[context.dataIndex]; | ||||||
|  | 							return [v.v]; | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					//mode: 'index', | ||||||
|  | 					animation: { | ||||||
|  | 						duration: 0, | ||||||
|  | 					}, | ||||||
|  | 					external: externalTooltipHandler, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | watch(() => props.src, () => { | ||||||
|  | 	fetching = true; | ||||||
|  | 	renderChart(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | onMounted(async () => { | ||||||
|  | 	renderChart(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
							
								
								
									
										201
									
								
								packages/frontend/src/pages/user/activity.pv.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								packages/frontend/src/pages/user/activity.pv.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | ||||||
|  | <template> | ||||||
|  | <div> | ||||||
|  | 	<MkLoading v-if="fetching"/> | ||||||
|  | 	<div v-show="!fetching" :class="$style.root" class="_panel"> | ||||||
|  | 		<canvas ref="chartEl"></canvas> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; | ||||||
|  | import { Chart } from 'chart.js'; | ||||||
|  | import { enUS } from 'date-fns/locale'; | ||||||
|  | import tinycolor from 'tinycolor2'; | ||||||
|  | import * as misskey from 'misskey-js'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import 'chartjs-adapter-date-fns'; | ||||||
|  | import { defaultStore } from '@/store'; | ||||||
|  | import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | ||||||
|  | import gradient from 'chartjs-plugin-gradient'; | ||||||
|  | import { chartVLine } from '@/scripts/chart-vline'; | ||||||
|  | import { alpha } from '@/scripts/color'; | ||||||
|  | import { initChart } from '@/scripts/init-chart'; | ||||||
|  | 
 | ||||||
|  | initChart(); | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	user: misskey.entities.User; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const chartEl = $ref<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/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' }); | ||||||
|  | 
 | ||||||
|  | 	const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; | ||||||
|  | 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  | 
 | ||||||
|  | 	// フォントカラー | ||||||
|  | 	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); | ||||||
|  | 
 | ||||||
|  | 	const colorUser = '#3498db'; | ||||||
|  | 	const colorVisitor = '#2ecc71'; | ||||||
|  | 
 | ||||||
|  | 	chartInstance = new Chart(chartEl, { | ||||||
|  | 		type: 'bar', | ||||||
|  | 		data: { | ||||||
|  | 			datasets: [{ | ||||||
|  | 				parsing: false, | ||||||
|  | 				label: 'UPV (user)', | ||||||
|  | 				data: format(raw.upv.user).slice().reverse(), | ||||||
|  | 				pointRadius: 0, | ||||||
|  | 				borderWidth: 0, | ||||||
|  | 				borderJoinStyle: 'round', | ||||||
|  | 				borderRadius: 4, | ||||||
|  | 				backgroundColor: colorUser, | ||||||
|  | 				barPercentage: 0.7, | ||||||
|  | 				categoryPercentage: 1, | ||||||
|  | 				fill: true, | ||||||
|  | 			}, { | ||||||
|  | 				parsing: false, | ||||||
|  | 				label: 'UPV (visitor)', | ||||||
|  | 				data: format(raw.upv.visitor).slice().reverse(), | ||||||
|  | 				pointRadius: 0, | ||||||
|  | 				borderWidth: 0, | ||||||
|  | 				borderJoinStyle: 'round', | ||||||
|  | 				borderRadius: 4, | ||||||
|  | 				backgroundColor: colorVisitor, | ||||||
|  | 				barPercentage: 0.7, | ||||||
|  | 				categoryPercentage: 1, | ||||||
|  | 				fill: true, | ||||||
|  | 			}], | ||||||
|  | 		}, | ||||||
|  | 		options: { | ||||||
|  | 			aspectRatio: 2.5, | ||||||
|  | 			layout: { | ||||||
|  | 				padding: { | ||||||
|  | 					left: 0, | ||||||
|  | 					right: 8, | ||||||
|  | 					top: 0, | ||||||
|  | 					bottom: 0, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			scales: { | ||||||
|  | 				x: { | ||||||
|  | 					type: 'time', | ||||||
|  | 					offset: true, | ||||||
|  | 					stacked: true, | ||||||
|  | 					time: { | ||||||
|  | 						stepSize: 1, | ||||||
|  | 						unit: 'day', | ||||||
|  | 					}, | ||||||
|  | 					grid: { | ||||||
|  | 						display: false, | ||||||
|  | 						color: gridColor, | ||||||
|  | 						borderColor: 'rgb(0, 0, 0, 0)', | ||||||
|  | 					}, | ||||||
|  | 					ticks: { | ||||||
|  | 						display: true, | ||||||
|  | 						maxRotation: 0, | ||||||
|  | 						autoSkipPadding: 8, | ||||||
|  | 					}, | ||||||
|  | 					adapters: { | ||||||
|  | 						date: { | ||||||
|  | 							locale: enUS, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				y: { | ||||||
|  | 					position: 'left', | ||||||
|  | 					stacked: true, | ||||||
|  | 					suggestedMax: 10, | ||||||
|  | 					grid: { | ||||||
|  | 						display: true, | ||||||
|  | 						color: gridColor, | ||||||
|  | 						borderColor: 'rgb(0, 0, 0, 0)', | ||||||
|  | 					}, | ||||||
|  | 					ticks: { | ||||||
|  | 						display: true, | ||||||
|  | 						//mirror: true, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			interaction: { | ||||||
|  | 				intersect: false, | ||||||
|  | 				mode: 'index', | ||||||
|  | 			}, | ||||||
|  | 			animation: false, | ||||||
|  | 			plugins: { | ||||||
|  | 				title: { | ||||||
|  | 					display: true, | ||||||
|  | 					text: 'Unique PV', | ||||||
|  | 					padding: { | ||||||
|  | 						left: 0, | ||||||
|  | 						right: 0, | ||||||
|  | 						top: 0, | ||||||
|  | 						bottom: 12, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				legend: { | ||||||
|  | 					display: true, | ||||||
|  | 					position: 'bottom', | ||||||
|  | 					padding: { | ||||||
|  | 						left: 0, | ||||||
|  | 						right: 0, | ||||||
|  | 						top: 8, | ||||||
|  | 						bottom: 0, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				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> | ||||||
							
								
								
									
										29
									
								
								packages/frontend/src/pages/user/activity.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/frontend/src/pages/user/activity.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | <template> | ||||||
|  | <MkSpacer :content-max="700"> | ||||||
|  | 	<MkFolder class="item"> | ||||||
|  | 		<template #header>Heatmap</template> | ||||||
|  | 		<XHeatmap :user="user" :src="'notes'"/> | ||||||
|  | 	</MkFolder> | ||||||
|  | 	<MkFolder class="item"> | ||||||
|  | 		<template #header>PV</template> | ||||||
|  | 		<XPv :user="user"/> | ||||||
|  | 	</MkFolder> | ||||||
|  | </MkSpacer> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | import * as misskey from 'misskey-js'; | ||||||
|  | import XHeatmap from './activity.heatmap.vue'; | ||||||
|  | import XPv from './activity.pv.vue'; | ||||||
|  | import MkFolder from '@/components/MkFolder.vue'; | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	user: misskey.entities.User; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
|  | @ -33,10 +33,16 @@ let chartSrc = $ref('per-user-notes'); | ||||||
| function showMenu(ev: MouseEvent) { | function showMenu(ev: MouseEvent) { | ||||||
| 	os.popupMenu([{ | 	os.popupMenu([{ | ||||||
| 		text: i18n.ts.notes, | 		text: i18n.ts.notes, | ||||||
| 		active: true, | 		active: chartSrc === 'per-user-notes', | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			chartSrc = 'per-user-notes'; | 			chartSrc = 'per-user-notes'; | ||||||
| 		}, | 		}, | ||||||
|  | 	}, { | ||||||
|  | 		text: i18n.ts.numberOfProfileView, | ||||||
|  | 		active: chartSrc === 'per-user-pv', | ||||||
|  | 		action: () => { | ||||||
|  | 			chartSrc = 'per-user-pv'; | ||||||
|  | 		}, | ||||||
| 	}, /*, { | 	}, /*, { | ||||||
| 		text: i18n.ts.following, | 		text: i18n.ts.following, | ||||||
| 		action: () => { | 		action: () => { | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| 		<Transition name="fade" mode="out-in"> | 		<Transition name="fade" mode="out-in"> | ||||||
| 			<div v-if="user"> | 			<div v-if="user"> | ||||||
| 				<XHome v-if="tab === 'home'" :user="user"/> | 				<XHome v-if="tab === 'home'" :user="user"/> | ||||||
|  | 				<XActivity v-else-if="tab === 'activity'" :user="user"/> | ||||||
| 				<XReactions v-else-if="tab === 'reactions'" :user="user"/> | 				<XReactions v-else-if="tab === 'reactions'" :user="user"/> | ||||||
| 				<XClips v-else-if="tab === 'clips'" :user="user"/> | 				<XClips v-else-if="tab === 'clips'" :user="user"/> | ||||||
| 				<XPages v-else-if="tab === 'pages'" :user="user"/> | 				<XPages v-else-if="tab === 'pages'" :user="user"/> | ||||||
|  | @ -32,6 +33,7 @@ import { i18n } from '@/i18n'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
| 
 | 
 | ||||||
| const XHome = defineAsyncComponent(() => import('./home.vue')); | const XHome = defineAsyncComponent(() => import('./home.vue')); | ||||||
|  | const XActivity = defineAsyncComponent(() => import('./activity.vue')); | ||||||
| const XReactions = defineAsyncComponent(() => import('./reactions.vue')); | const XReactions = defineAsyncComponent(() => import('./reactions.vue')); | ||||||
| const XClips = defineAsyncComponent(() => import('./clips.vue')); | const XClips = defineAsyncComponent(() => import('./clips.vue')); | ||||||
| const XPages = defineAsyncComponent(() => import('./pages.vue')); | const XPages = defineAsyncComponent(() => import('./pages.vue')); | ||||||
|  | @ -70,6 +72,10 @@ const headerTabs = $computed(() => user ? [{ | ||||||
| 	key: 'home', | 	key: 'home', | ||||||
| 	title: i18n.ts.overview, | 	title: i18n.ts.overview, | ||||||
| 	icon: 'ti ti-home', | 	icon: 'ti ti-home', | ||||||
|  | }, { | ||||||
|  | 	key: 'activity', | ||||||
|  | 	title: i18n.ts.activity, | ||||||
|  | 	icon: 'ti ti-chart-line', | ||||||
| }, ...($i && ($i.id === user.id)) || user.publicReactions ? [{ | }, ...($i && ($i.id === user.id)) || user.publicReactions ? [{ | ||||||
| 	key: 'reactions', | 	key: 'reactions', | ||||||
| 	title: i18n.ts.reaction, | 	title: i18n.ts.reaction, | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								packages/frontend/src/scripts/init-chart.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/frontend/src/scripts/init-chart.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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, | ||||||
|  | 	); | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue