From 4fd386c3dc5346576d52c9baaa29574d07dc6d86 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 29 Jun 2022 15:41:06 +0900
Subject: chore(client): tweak client

---
 packages/client/src/components/instance-stats.vue | 221 ++++++++++++++++------
 1 file changed, 164 insertions(+), 57 deletions(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index f386a8de9a..9a1769a3a1 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -1,81 +1,188 @@
 <template>
 <div class="zbcjwnqg">
-	<div class="selects" style="display: flex;">
-		<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
-			<optgroup :label="$ts.federation">
-				<option value="federation">{{ $ts._charts.federation }}</option>
-				<option value="ap-request">{{ $ts._charts.apRequest }}</option>
-			</optgroup>
-			<optgroup :label="$ts.users">
-				<option value="users">{{ $ts._charts.usersIncDec }}</option>
-				<option value="users-total">{{ $ts._charts.usersTotal }}</option>
-				<option value="active-users">{{ $ts._charts.activeUsers }}</option>
-			</optgroup>
-			<optgroup :label="$ts.notes">
-				<option value="notes">{{ $ts._charts.notesIncDec }}</option>
-				<option value="local-notes">{{ $ts._charts.localNotesIncDec }}</option>
-				<option value="remote-notes">{{ $ts._charts.remoteNotesIncDec }}</option>
-				<option value="notes-total">{{ $ts._charts.notesTotal }}</option>
-			</optgroup>
-			<optgroup :label="$ts.drive">
-				<option value="drive-files">{{ $ts._charts.filesIncDec }}</option>
-				<option value="drive">{{ $ts._charts.storageUsageIncDec }}</option>
-			</optgroup>
-		</MkSelect>
-		<MkSelect v-model="chartSpan" style="margin: 0 0 0 10px;">
-			<option value="hour">{{ $ts.perHour }}</option>
-			<option value="day">{{ $ts.perDay }}</option>
-		</MkSelect>
+	<div class="main">
+		<div class="body">
+			<div class="selects" style="display: flex;">
+				<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
+					<optgroup :label="$ts.federation">
+						<option value="federation">{{ $ts._charts.federation }}</option>
+						<option value="ap-request">{{ $ts._charts.apRequest }}</option>
+					</optgroup>
+					<optgroup :label="$ts.users">
+						<option value="users">{{ $ts._charts.usersIncDec }}</option>
+						<option value="users-total">{{ $ts._charts.usersTotal }}</option>
+						<option value="active-users">{{ $ts._charts.activeUsers }}</option>
+					</optgroup>
+					<optgroup :label="$ts.notes">
+						<option value="notes">{{ $ts._charts.notesIncDec }}</option>
+						<option value="local-notes">{{ $ts._charts.localNotesIncDec }}</option>
+						<option value="remote-notes">{{ $ts._charts.remoteNotesIncDec }}</option>
+						<option value="notes-total">{{ $ts._charts.notesTotal }}</option>
+					</optgroup>
+					<optgroup :label="$ts.drive">
+						<option value="drive-files">{{ $ts._charts.filesIncDec }}</option>
+						<option value="drive">{{ $ts._charts.storageUsageIncDec }}</option>
+					</optgroup>
+				</MkSelect>
+				<MkSelect v-model="chartSpan" style="margin: 0 0 0 10px;">
+					<option value="hour">{{ $ts.perHour }}</option>
+					<option value="day">{{ $ts.perDay }}</option>
+				</MkSelect>
+			</div>
+			<div class="chart">
+				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart>
+			</div>
+		</div>
 	</div>
-	<div class="chart">
-		<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart>
+	<div class="subpub">
+		<div class="sub">
+			<div class="title">Sub</div>
+			<canvas ref="subDoughnutEl"></canvas>
+		</div>
+		<div class="pub">
+			<div class="title">Pub</div>
+			<canvas ref="pubDoughnutEl"></canvas>
+		</div>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import {
+	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 MkChart from '@/components/chart.vue';
+import { useChartTooltip } from '@/scripts/use-chart-tooltip';
+import * as os from '@/os';
 
-export default defineComponent({
-	components: {
-		MkSelect,
-		MkChart,
-	},
+Chart.register(
+	ArcElement,
+	LineElement,
+	BarElement,
+	PointElement,
+	BarController,
+	LineController,
+	DoughnutController,
+	CategoryScale,
+	LinearScale,
+	TimeScale,
+	Legend,
+	Title,
+	Tooltip,
+	SubTitle,
+	Filler,
+);
 
-	props: {
-		chartLimit: {
-			type: Number,
-			required: false,
-			default: 90
+const props = withDefaults(defineProps<{
+	chartLimit?: number;
+	detailed?: boolean;
+}>(), {
+	chartLimit: 90,
+});
+
+const chartSpan = $ref<'hour' | 'day'>('hour');
+const chartSrc = $ref('active-users');
+let subDoughnutEl = $ref<HTMLCanvasElement>();
+let pubDoughnutEl = $ref<HTMLCanvasElement>();
+
+const { handler: externalTooltipHandler1 } = useChartTooltip();
+const { handler: externalTooltipHandler2 } = useChartTooltip();
+
+function createDoughnut(chartEl, tooltip, data) {
+	return new Chart(chartEl, {
+		type: 'doughnut',
+		data: {
+			labels: data.map(x => x.name),
+			datasets: [{
+				backgroundColor: data.map(x => x.color),
+				data: data.map(x => x.value),
+			}],
 		},
-		detailed: {
-			type: Boolean,
-			required: false,
-			default: false
+		options: {
+			layout: {
+				padding: {
+					left: 8,
+					right: 8,
+					top: 8,
+					bottom: 8,
+				},
+			},
+			interaction: {
+				intersect: false,
+			},
+			plugins: {
+				legend: {
+					display: false,
+				},
+				tooltip: {
+					enabled: false,
+					mode: 'index',
+					animation: {
+						duration: 0,
+					},
+					external: tooltip,
+				},
+			},
 		},
-	},
-
-	setup() {
-		const chartSpan = ref<'hour' | 'day'>('hour');
-		const chartSrc = ref('active-users');
+	});
+}
 
-		return {
-			chartSrc,
-			chartSpan,
-		};
-	},
+onMounted(() => {
+	os.apiGet('federation/stats').then(fedStats => {
+		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowersCount }]));
+		createDoughnut(pubDoughnutEl, externalTooltipHandler1, fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowingCount }]));
+	});
 });
 </script>
 
 <style lang="scss" scoped>
 .zbcjwnqg {
-	> .selects {
+	> .main {
+		background: var(--panel);
+		border-radius: var(--radius);
+		padding: 24px;
+		margin-bottom: 16px;
+
+		> .body {
+			> .chart {
+				padding: 8px 0 0 0;
+			}
+		}
 	}
 
-	> .chart {
-		padding: 8px 0 0 0;
+	> .subpub {
+		display: flex;
+		gap: 16px;
+
+		> .sub, > .pub {
+			position: relative;
+			background: var(--panel);
+			border-radius: var(--radius);
+			padding: 24px;
+
+			> .title {
+				position: absolute;
+				top: 24px;
+				left: 24px;
+			}
+		}
 	}
 }
 </style>
-- 
cgit v1.2.3-freya


From ed41d542bb8894f2eaca42cd7cc08246089e0490 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 30 Jun 2022 20:15:14 +0900
Subject: chore(client): tweak ui

---
 .../src/server/api/endpoints/federation/stats.ts     |  5 +++--
 packages/client/src/components/instance-stats.vue    | 20 ++++++++++----------
 packages/client/src/pages/admin/overview.pie.vue     | 14 +++++++-------
 packages/client/src/pages/admin/overview.vue         |  6 +++---
 4 files changed, 23 insertions(+), 22 deletions(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts
index d3c2659088..cbe47dc7cb 100644
--- a/packages/backend/src/server/api/endpoints/federation/stats.ts
+++ b/packages/backend/src/server/api/endpoints/federation/stats.ts
@@ -15,6 +15,7 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 	},
 	required: [],
 } as const;
@@ -29,7 +30,7 @@ export default define(meta, paramDef, async (ps) => {
 			order: {
 				followersCount: 'DESC',
 			},
-			take: 10,
+			take: ps.limit,
 		}),
 		Instances.find({
 			where: {
@@ -38,7 +39,7 @@ export default define(meta, paramDef, async (ps) => {
 			order: {
 				followingCount: 'DESC',
 			},
-			take: 10,
+			take: ps.limit,
 		}),
 		Followings.count({
 			where: {
diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index 9a1769a3a1..1646a7e93e 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -112,21 +112,21 @@ function createDoughnut(chartEl, tooltip, data) {
 			labels: data.map(x => x.name),
 			datasets: [{
 				backgroundColor: data.map(x => x.color),
+				borderWidth: 0,
+				spacing: 4,
+				hoverOffset: 4,
 				data: data.map(x => x.value),
 			}],
 		},
 		options: {
 			layout: {
 				padding: {
-					left: 8,
-					right: 8,
-					top: 8,
-					bottom: 8,
+					left: 16,
+					right: 16,
+					top: 16,
+					bottom: 16,
 				},
 			},
-			interaction: {
-				intersect: false,
-			},
 			plugins: {
 				legend: {
 					display: false,
@@ -145,9 +145,9 @@ function createDoughnut(chartEl, tooltip, data) {
 }
 
 onMounted(() => {
-	os.apiGet('federation/stats').then(fedStats => {
-		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowersCount }]));
-		createDoughnut(pubDoughnutEl, externalTooltipHandler1, fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowingCount }]));
+	os.apiGet('federation/stats', { limit: 15 }).then(fedStats => {
+		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
+		createDoughnut(pubDoughnutEl, externalTooltipHandler1, fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }]));
 	});
 });
 </script>
diff --git a/packages/client/src/pages/admin/overview.pie.vue b/packages/client/src/pages/admin/overview.pie.vue
index d14b3cc6db..41a5e53ae3 100644
--- a/packages/client/src/pages/admin/overview.pie.vue
+++ b/packages/client/src/pages/admin/overview.pie.vue
@@ -64,21 +64,21 @@ onMounted(() => {
 			labels: props.data.map(x => x.name),
 			datasets: [{
 				backgroundColor: props.data.map(x => x.color),
+				borderWidth: 0,
+				spacing: 4,
+				hoverOffset: 4,
 				data: props.data.map(x => x.value),
 			}],
 		},
 		options: {
 			layout: {
 				padding: {
-					left: 8,
-					right: 8,
-					top: 8,
-					bottom: 8,
+					left: 16,
+					right: 16,
+					top: 16,
+					bottom: 16,
 				},
 			},
-			interaction: {
-				intersect: false,
-			},
 			plugins: {
 				legend: {
 					display: false,
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index 6ccee8aea2..393ee66452 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -123,12 +123,12 @@
 				<div class="body">
 					<div class="chart deliver">
 						<div class="title">Sub</div>
-						<XPie :data="fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowersCount }])"/>
+						<XPie :data="fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }])"/>
 						<div class="subTitle">Top 10</div>
 					</div>
 					<div class="chart inbox">
 						<div class="title">Pub</div>
-						<XPie :data="fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#808080', value: fedStats.otherFollowingCount }])"/>
+						<XPie :data="fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }])"/>
 						<div class="subTitle">Top 10</div>
 					</div>
 				</div>
@@ -411,7 +411,7 @@ onMounted(async () => {
 		federationSubActiveDiff = chart.subActive[0] - chart.subActive[1];
 	});
 
-	os.apiGet('federation/stats').then(res => {
+	os.apiGet('federation/stats', { limit: 10 }).then(res => {
 		fedStats = res;
 	});
 
-- 
cgit v1.2.3-freya


From 4c2cd3c8d58649c4a2d891caf088dcbee99596a3 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 30 Jun 2022 21:38:34 +0900
Subject: chore(client): tweak ui

---
 packages/client/src/components/instance-stats.vue |   6 +-
 packages/client/src/pages/admin/overview.pie.vue  |   6 +-
 packages/client/src/pages/gallery/edit.vue        |  53 ++++++-----
 packages/client/src/pages/gallery/index.vue       |  33 +++++--
 packages/client/src/pages/gallery/post.vue        | 110 +++++++++++-----------
 packages/client/src/pages/user/gallery.vue        |  40 +++-----
 6 files changed, 128 insertions(+), 120 deletions(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index 1646a7e93e..2f83b2f897 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -112,9 +112,9 @@ function createDoughnut(chartEl, tooltip, data) {
 			labels: data.map(x => x.name),
 			datasets: [{
 				backgroundColor: data.map(x => x.color),
-				borderWidth: 0,
-				spacing: 4,
-				hoverOffset: 4,
+				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'),
+				borderWidth: 2,
+				hoverOffset: 0,
 				data: data.map(x => x.value),
 			}],
 		},
diff --git a/packages/client/src/pages/admin/overview.pie.vue b/packages/client/src/pages/admin/overview.pie.vue
index 41a5e53ae3..667f236d9b 100644
--- a/packages/client/src/pages/admin/overview.pie.vue
+++ b/packages/client/src/pages/admin/overview.pie.vue
@@ -64,9 +64,9 @@ onMounted(() => {
 			labels: props.data.map(x => x.name),
 			datasets: [{
 				backgroundColor: props.data.map(x => x.color),
-				borderWidth: 0,
-				spacing: 4,
-				hoverOffset: 4,
+				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'),
+				borderWidth: 2,
+				hoverOffset: 0,
 				data: props.data.map(x => x.value),
 			}],
 		},
diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index fa3063bde0..1de8328fea 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -1,30 +1,33 @@
 <template>
-<div>
-	<FormSuspense :p="init">
-		<FormInput v-model="title">
-			<template #label>{{ $ts.title }}</template>
-		</FormInput>
-
-		<FormTextarea v-model="description" :max="500">
-			<template #label>{{ $ts.description }}</template>
-		</FormTextarea>
-
-		<div class="">
-			<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
-				<div class="name">{{ file.name }}</div>
-				<button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button>
+<MkStickyContainer>
+	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+		<FormSuspense :p="init">
+			<FormInput v-model="title">
+				<template #label>{{ $ts.title }}</template>
+			</FormInput>
+
+			<FormTextarea v-model="description" :max="500">
+				<template #label>{{ $ts.description }}</template>
+			</FormTextarea>
+
+			<div class="">
+				<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
+					<div class="name">{{ file.name }}</div>
+					<button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button>
+				</div>
+				<FormButton primary @click="selectFile"><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton>
 			</div>
-			<FormButton primary @click="selectFile"><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton>
-		</div>
 
-		<FormSwitch v-model="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch>
+			<FormSwitch v-model="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch>
 
-		<FormButton v-if="postId" primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
-		<FormButton v-else primary @click="save"><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton>
+			<FormButton v-if="postId" primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+			<FormButton v-else primary @click="save"><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton>
 
-		<FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
-	</FormSuspense>
-</div>
+			<FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
+		</FormSuspense>
+	</MkSpacer>
+</MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
@@ -71,7 +74,7 @@ async function save() {
 			fileIds: files.map(file => file.id),
 			isSensitive: isSensitive,
 		});
-		mainRouter.push(`/gallery/${props.postId}`);
+		router.push(`/gallery/${props.postId}`);
 	} else {
 		const created = await os.apiWithDialog('gallery/posts/create', {
 			title: title,
@@ -92,7 +95,7 @@ async function del() {
 	await os.apiWithDialog('gallery/posts/delete', {
 		postId: props.postId,
 	});
-	mainRouter.push('/gallery');
+	router.push('/gallery');
 }
 
 watch(() => props.postId, () => {
@@ -113,9 +116,11 @@ const headerTabs = $computed(() => []);
 definePageMetadata(computed(() => props.postId ? {
 	title: i18n.ts.edit,
 	icon: 'fas fa-pencil-alt',
+	bg: 'var(--bg)',
 } : {
 	title: i18n.ts.postToGallery,
 	icon: 'fas fa-pencil-alt',
+	bg: 'var(--bg)',
 }));
 </script>
 
diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue
index b26470dbe9..1eb6ce22f8 100644
--- a/packages/client/src/pages/gallery/index.vue
+++ b/packages/client/src/pages/gallery/index.vue
@@ -1,14 +1,8 @@
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="1400">
 		<div class="_root">
-			<MkTab v-if="$i" v-model="tab">
-				<option value="explore"><i class="fas fa-icons"></i> {{ $ts.gallery }}</option>
-				<option value="liked"><i class="fas fa-heart"></i> {{ $ts._gallery.liked }}</option>
-				<option value="my"><i class="fas fa-edit"></i> {{ $ts._gallery.my }}</option>
-			</MkTab>
-
 			<div v-if="tab === 'explore'">
 				<MkFolder class="_gap">
 					<template #header><i class="fas fa-clock"></i>{{ $ts.recentPosts }}</template>
@@ -60,6 +54,9 @@ import number from '@/filters/number';
 import * as os from '@/os';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
+import { useRouter } from '@/router';
+
+const router = useRouter();
 
 const props = defineProps<{
 	tag?: string;
@@ -100,9 +97,27 @@ watch(() => props.tag, () => {
 	if (tagsRef) tagsRef.tags.toggleContent(props.tag == null);
 });
 
-const headerActions = $computed(() => []);
+const headerActions = $computed(() => [{
+	icon: 'fas fa-plus',
+	text: i18n.ts.create,
+	handler: () => {
+		router.push('/gallery/new');
+	},
+}]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = $computed(() => [{
+	key: 'explore',
+	title: i18n.ts.gallery,
+	icon: 'fas fa-icons',
+}, {
+	key: 'liked',
+	title: i18n.ts._gallery.liked,
+	icon: 'fas fa-heart',
+}, {
+	key: 'my',
+	title: i18n.ts._gallery.my,
+	icon: 'fas fa-edit',
+}]);
 
 definePageMetadata({
 	title: i18n.ts.gallery,
diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue
index d5f3253b3e..6651c3e28f 100644
--- a/packages/client/src/pages/gallery/post.vue
+++ b/packages/client/src/pages/gallery/post.vue
@@ -1,52 +1,57 @@
 <template>
-<div class="_root">
-	<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
-		<div v-if="post" class="rkxwuolj">
-			<div class="files">
-				<div v-for="file in post.files" :key="file.id" class="file">
-					<img :src="file.url"/>
-				</div>
-			</div>
-			<div class="body _block">
-				<div class="title">{{ post.title }}</div>
-				<div class="description"><Mfm :text="post.description"/></div>
-				<div class="info">
-					<i class="fas fa-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
-				</div>
-				<div class="actions">
-					<div class="like">
-						<MkButton v-if="post.isLiked" v-tooltip="$ts._gallery.unlike" class="button" primary @click="unlike()"><i class="fas fa-heart"></i><span v-if="post.likedCount > 0" class="count">{{ post.likedCount }}</span></MkButton>
-						<MkButton v-else v-tooltip="$ts._gallery.like" class="button" @click="like()"><i class="far fa-heart"></i><span v-if="post.likedCount > 0" class="count">{{ post.likedCount }}</span></MkButton>
+<MkStickyContainer>
+	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<MkSpacer :content-max="1000" :margin-min="16" :margin-max="32">
+		<div class="_root">
+			<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
+				<div v-if="post" class="rkxwuolj">
+					<div class="files">
+						<div v-for="file in post.files" :key="file.id" class="file">
+							<img :src="file.url"/>
+						</div>
 					</div>
-					<div class="other">
-						<button v-if="$i && $i.id === post.user.id" v-tooltip="$ts.edit" v-click-anime class="_button" @click="edit"><i class="fas fa-pencil-alt fa-fw"></i></button>
-						<button v-tooltip="$ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="fas fa-retweet fa-fw"></i></button>
-						<button v-tooltip="$ts.share" v-click-anime class="_button" @click="share"><i class="fas fa-share-alt fa-fw"></i></button>
-					</div>
-				</div>
-				<div class="user">
-					<MkAvatar :user="post.user" class="avatar"/>
-					<div class="name">
-						<MkUserName :user="post.user" style="display: block;"/>
-						<MkAcct :user="post.user"/>
+					<div class="body _block">
+						<div class="title">{{ post.title }}</div>
+						<div class="description"><Mfm :text="post.description"/></div>
+						<div class="info">
+							<i class="fas fa-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
+						</div>
+						<div class="actions">
+							<div class="like">
+								<MkButton v-if="post.isLiked" v-tooltip="$ts._gallery.unlike" class="button" primary @click="unlike()"><i class="fas fa-heart"></i><span v-if="post.likedCount > 0" class="count">{{ post.likedCount }}</span></MkButton>
+								<MkButton v-else v-tooltip="$ts._gallery.like" class="button" @click="like()"><i class="far fa-heart"></i><span v-if="post.likedCount > 0" class="count">{{ post.likedCount }}</span></MkButton>
+							</div>
+							<div class="other">
+								<button v-if="$i && $i.id === post.user.id" v-tooltip="$ts.edit" v-click-anime class="_button" @click="edit"><i class="fas fa-pencil-alt fa-fw"></i></button>
+								<button v-tooltip="$ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="fas fa-retweet fa-fw"></i></button>
+								<button v-tooltip="$ts.share" v-click-anime class="_button" @click="share"><i class="fas fa-share-alt fa-fw"></i></button>
+							</div>
+						</div>
+						<div class="user">
+							<MkAvatar :user="post.user" class="avatar"/>
+							<div class="name">
+								<MkUserName :user="post.user" style="display: block;"/>
+								<MkAcct :user="post.user"/>
+							</div>
+							<MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
+						</div>
 					</div>
-					<MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
+					<MkAd :prefer="['horizontal', 'horizontal-big']"/>
+					<MkContainer :max-height="300" :foldable="true" class="other">
+						<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
+						<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
+							<div class="sdrarzaf">
+								<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
+							</div>
+						</MkPagination>
+					</MkContainer>
 				</div>
-			</div>
-			<MkAd :prefer="['horizontal', 'horizontal-big']"/>
-			<MkContainer :max-height="300" :foldable="true" class="other">
-				<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
-				<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
-					<div class="sdrarzaf">
-						<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
-					</div>
-				</MkPagination>
-			</MkContainer>
+				<MkError v-else-if="error" @retry="fetch()"/>
+				<MkLoading v-else/>
+			</transition>
 		</div>
-		<MkError v-else-if="error" @retry="fetch()"/>
-		<MkLoading v-else/>
-	</transition>
-</div>
+	</MkSpacer>
+</MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
@@ -133,23 +138,18 @@ function edit() {
 
 watch(() => props.postId, fetchPost, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = $computed(() => [{
+	icon: 'fas fa-pencil-alt',
+	text: i18n.ts.edit,
+	handler: edit,
+}]);
 
 const headerTabs = $computed(() => []);
 
 definePageMetadata(computed(() => post ? {
 	title: post.title,
 	avatar: post.user,
-	path: `/gallery/${post.id}`,
-	share: {
-		title: post.title,
-		text: post.description,
-	},
-	actions: [{
-		icon: 'fas fa-pencil-alt',
-		text: i18n.ts.edit,
-		handler: edit,
-	}],
+	bg: 'var(--bg)',
 } : null));
 </script>
 
diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue
index 07dda4a292..6af28d455b 100644
--- a/packages/client/src/pages/user/gallery.vue
+++ b/packages/client/src/pages/user/gallery.vue
@@ -8,36 +8,24 @@
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as misskey from 'misskey-js';
 import MkGalleryPostPreview from '@/components/gallery-post-preview.vue';
 import MkPagination from '@/components/ui/pagination.vue';
 
-export default defineComponent({
-	components: {
-		MkPagination,
-		MkGalleryPostPreview,
-	},
-
-	props: {
-		user: {
-			type: Object,
-			required: true
-		},
-	},
-
-	data() {
-		return {
-			pagination: {
-				endpoint: 'users/gallery/posts' as const,
-				limit: 6,
-				params: computed(() => ({
-					userId: this.user.id
-				})),
-			},
-		};
-	},
+const props = withDefaults(defineProps<{
+	user: misskey.entities.User;
+}>(), {
 });
+
+const pagination = {
+	endpoint: 'users/gallery/posts' as const,
+	limit: 6,
+	params: computed(() => ({
+		userId: props.user.id,
+	})),
+};
 </script>
 
 <style lang="scss" scoped>
-- 
cgit v1.2.3-freya


From cb697cf582666a0c2a954a964045fb447f0e639e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 30 Jun 2022 22:02:08 +0900
Subject: chore(client): tweak ui

---
 packages/client/src/components/instance-stats.vue | 29 ++++++++++++++++++++---
 packages/client/src/pages/admin/overview.pie.vue  |  8 ++++++-
 packages/client/src/pages/admin/overview.vue      | 26 ++++++++++++++++----
 3 files changed, 54 insertions(+), 9 deletions(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index 2f83b2f897..c210371c6a 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -106,7 +106,7 @@ const { handler: externalTooltipHandler1 } = useChartTooltip();
 const { handler: externalTooltipHandler2 } = useChartTooltip();
 
 function createDoughnut(chartEl, tooltip, data) {
-	return new Chart(chartEl, {
+	const chartInstance = new Chart(chartEl, {
 		type: 'doughnut',
 		data: {
 			labels: data.map(x => x.name),
@@ -127,6 +127,12 @@ function createDoughnut(chartEl, tooltip, data) {
 					bottom: 16,
 				},
 			},
+			onClick: (ev) => {
+				const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0];
+				if (hit && data[hit.index].onClick) {
+					data[hit.index].onClick();
+				}
+			},
 			plugins: {
 				legend: {
 					display: false,
@@ -142,12 +148,29 @@ function createDoughnut(chartEl, tooltip, data) {
 			},
 		},
 	});
+
+	return chartInstance;
 }
 
 onMounted(() => {
 	os.apiGet('federation/stats', { limit: 15 }).then(fedStats => {
-		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
-		createDoughnut(pubDoughnutEl, externalTooltipHandler1, fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }]));
+		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
+			name: x.host,
+			color: x.themeColor,
+			value: x.followersCount,
+			onClick: () => {
+				os.pageWindow(`/instance-info/${x.host}`);
+			},
+		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
+
+		createDoughnut(pubDoughnutEl, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
+			name: x.host,
+			color: x.themeColor,
+			value: x.followingCount,
+			onClick: () => {
+				os.pageWindow(`/instance-info/${x.host}`);
+			},
+		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }]));
 	});
 });
 </script>
diff --git a/packages/client/src/pages/admin/overview.pie.vue b/packages/client/src/pages/admin/overview.pie.vue
index 667f236d9b..d3b2032876 100644
--- a/packages/client/src/pages/admin/overview.pie.vue
+++ b/packages/client/src/pages/admin/overview.pie.vue
@@ -45,7 +45,7 @@ Chart.register(
 );
 
 const props = defineProps<{
-	data: { name: string; value: number; color: string; }[];
+	data: { name: string; value: number; color: string; onClick?: () => void }[];
 }>();
 
 const chartEl = ref<HTMLCanvasElement>(null);
@@ -79,6 +79,12 @@ onMounted(() => {
 					bottom: 16,
 				},
 			},
+			onClick: (ev) => {
+				const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0];
+				if (hit && props.data[hit.index].onClick) {
+					props.data[hit.index].onClick();
+				}
+			},
 			plugins: {
 				legend: {
 					display: false,
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index 393ee66452..d2fa4e0e45 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -119,16 +119,16 @@
 					</MkTagCloud>
 				</div>
 			</div>
-			<div v-if="fedStats" class="container federationPies">
+			<div v-if="topSubInstancesForPie && topPubInstancesForPie" class="container federationPies">
 				<div class="body">
 					<div class="chart deliver">
 						<div class="title">Sub</div>
-						<XPie :data="fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }])"/>
+						<XPie :data="topSubInstancesForPie"/>
 						<div class="subTitle">Top 10</div>
 					</div>
 					<div class="chart inbox">
 						<div class="title">Pub</div>
-						<XPie :data="fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }])"/>
+						<XPie :data="topPubInstancesForPie"/>
 						<div class="subTitle">Top 10</div>
 					</div>
 				</div>
@@ -200,7 +200,8 @@ const rootEl = $ref<HTMLElement>();
 const chartEl = $ref<HTMLCanvasElement>(null);
 let stats: any = $ref(null);
 let serverInfo: any = $ref(null);
-let fedStats: any = $ref(null);
+let topSubInstancesForPie: any = $ref(null);
+let topPubInstancesForPie: any = $ref(null);
 let usersComparedToThePrevDay: any = $ref(null);
 let notesComparedToThePrevDay: any = $ref(null);
 let federationPubActive = $ref<number | null>(null);
@@ -412,7 +413,22 @@ onMounted(async () => {
 	});
 
 	os.apiGet('federation/stats', { limit: 10 }).then(res => {
-		fedStats = res;
+		topSubInstancesForPie = fedStats.topSubInstances.map(x => ({
+			name: x.host,
+			color: x.themeColor,
+			value: x.followersCount,
+			onClick: () => {
+				os.pageWindow(`/instance-info/${x.host}`);
+			},
+		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]);
+		topPubInstancesForPie = fedStats.topPubInstances.map(x => ({
+			name: x.host,
+			color: x.themeColor,
+			value: x.followingCount,
+			onClick: () => {
+				os.pageWindow(`/instance-info/${x.host}`);
+			},
+		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }]);
 	});
 
 	os.api('admin/server-info').then(serverInfoResponse => {
-- 
cgit v1.2.3-freya


From 80a033c1cfb772376e2afb6e82bd007cb6abc745 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 1 Jul 2022 17:08:45 +0900
Subject: chore(client): tweak style

---
 packages/client/src/components/instance-stats.vue | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index c210371c6a..a03993752f 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -119,6 +119,7 @@ function createDoughnut(chartEl, tooltip, data) {
 			}],
 		},
 		options: {
+			maintainAspectRatio: false,
 			layout: {
 				padding: {
 					left: 16,
@@ -195,10 +196,13 @@ onMounted(() => {
 		gap: 16px;
 
 		> .sub, > .pub {
+			flex: 1;
+			min-width: 0;
 			position: relative;
 			background: var(--panel);
 			border-radius: var(--radius);
 			padding: 24px;
+			max-height: 300px;
 
 			> .title {
 				position: absolute;
@@ -206,6 +210,10 @@ onMounted(() => {
 				left: 24px;
 			}
 		}
+
+		@media (max-width: 600px) {
+			flex-direction: column;
+		}
 	}
 }
 </style>
-- 
cgit v1.2.3-freya


From a1cbffd14f807b3cb36c33edd0994023695b5897 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 5 Jul 2022 17:55:47 +0900
Subject: chore(client): tweak ui

---
 packages/client/src/components/instance-stats.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index a03993752f..a2a6353923 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -154,7 +154,7 @@ function createDoughnut(chartEl, tooltip, data) {
 }
 
 onMounted(() => {
-	os.apiGet('federation/stats', { limit: 15 }).then(fedStats => {
+	os.apiGet('federation/stats', { limit: 20 }).then(fedStats => {
 		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
-- 
cgit v1.2.3-freya


From 9426f37c3e398904390440a3948d93f8a90cc05f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 7 Jul 2022 17:55:47 +0900
Subject: chore(client): tweak ui

---
 packages/client/src/components/instance-stats.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'packages/client/src/components/instance-stats.vue')

diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index a2a6353923..1a811c2d87 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -154,7 +154,7 @@ function createDoughnut(chartEl, tooltip, data) {
 }
 
 onMounted(() => {
-	os.apiGet('federation/stats', { limit: 20 }).then(fedStats => {
+	os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
 		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
-- 
cgit v1.2.3-freya

