chore(client): tweak client
This commit is contained in:
		| @@ -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> | ||||
|   | ||||
| @@ -1,31 +1,35 @@ | ||||
| <template> | ||||
| <div class="igpposuu _monospace"> | ||||
| 	<div v-if="value === null" class="null">null</div> | ||||
| 	<div v-else-if="typeof value === 'boolean'" class="boolean">{{ value ? 'true' : 'false' }}</div> | ||||
| 	<div v-else-if="typeof value === 'boolean'" class="boolean" :class="{ true: value, false: !value }">{{ value ? 'true' : 'false' }}</div> | ||||
| 	<div v-else-if="typeof value === 'string'" class="string">"{{ value }}"</div> | ||||
| 	<div v-else-if="typeof value === 'number'" class="number">{{ number(value) }}</div> | ||||
| 	<div v-else-if="Array.isArray(value)" class="array"> | ||||
| 		<button @click="collapsed_ = !collapsed_">[ {{ collapsed_ ? '+' : '-' }} ]</button> | ||||
| 		<template v-if="!collapsed_"> | ||||
| 			<div v-for="i in value.length" class="element"> | ||||
| 				{{ i }}: <XValue :value="value[i - 1]" collapsed/> | ||||
| 			</div> | ||||
| 		</template> | ||||
| 	<div v-else-if="isArray(value) && isEmpty(value)" class="array empty">[]</div> | ||||
| 	<div v-else-if="isArray(value)" class="array"> | ||||
| 		<div v-for="i in value.length" class="element"> | ||||
| 			{{ i }}: <XValue :value="value[i - 1]" collapsed/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div v-else-if="typeof value === 'object'" class="object"> | ||||
| 		<button @click="collapsed_ = !collapsed_">{ {{ collapsed_ ? '+' : '-' }} }</button> | ||||
| 		<template v-if="!collapsed_"> | ||||
| 			<div v-for="k in Object.keys(value)" class="kv"> | ||||
| 				<div class="k">{{ k }}:</div> | ||||
| 				<div class="v"><XValue :value="value[k]" collapsed/></div> | ||||
| 	<div v-else-if="isObject(value) && isEmpty(value)" class="object empty">{}</div> | ||||
| 	<div v-else-if="isObject(value)" class="object"> | ||||
| 		<div v-for="k in Object.keys(value)" class="kv"> | ||||
| 			<button class="toggle _button" :class="{ visible: collapsable(value[k]) }" @click="collapsed[k] = !collapsed[k]">{{ collapsed[k] ? '+' : '-' }}</button> | ||||
| 			<div class="k">{{ k }}:</div> | ||||
| 			<div v-if="collapsed[k]" class="v"> | ||||
| 				<button class="_button" @click="collapsed[k] = !collapsed[k]"> | ||||
| 					<template v-if="typeof value[k] === 'string'">"..."</template> | ||||
| 					<template v-else-if="isArray(value[k])">[...]</template> | ||||
| 					<template v-else-if="isObject(value[k])">{...}</template> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</template> | ||||
| 			<div v-else class="v"><XValue :value="value[k]"/></div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, ref } from 'vue'; | ||||
| import { computed, defineComponent, reactive, ref } from 'vue'; | ||||
| import number from '@/filters/number'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| @@ -33,24 +37,44 @@ export default defineComponent({ | ||||
|  | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		collapsed: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	setup(props) { | ||||
| 		const collapsed_ = ref(props.collapsed); | ||||
| 		const collapsed = reactive({}); | ||||
|  | ||||
| 		if (isObject(props.value)) { | ||||
| 			for (const key in props.value) { | ||||
| 				collapsed[key] = collapsable(props.value[key]); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		function isObject(v): boolean { | ||||
| 			return typeof v === 'object' && !Array.isArray(v) && v !== null; | ||||
| 		} | ||||
|  | ||||
| 		function isArray(v): boolean { | ||||
| 			return Array.isArray(v); | ||||
| 		} | ||||
|  | ||||
| 		function isEmpty(v): boolean { | ||||
| 			return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0); | ||||
| 		} | ||||
|  | ||||
| 		function collapsable(v): boolean { | ||||
| 			return (isObject(v) || isArray(v)) && !isEmpty(v); | ||||
| 		} | ||||
|  | ||||
| 		return { | ||||
| 			number, | ||||
| 			collapsed_, | ||||
| 			collapsed, | ||||
| 			isObject, | ||||
| 			isArray, | ||||
| 			isEmpty, | ||||
| 			collapsable, | ||||
| 		}; | ||||
| 	} | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| @@ -66,6 +90,14 @@ export default defineComponent({ | ||||
| 	> .boolean { | ||||
| 		display: inline; | ||||
| 		color: var(--codeBoolean); | ||||
|  | ||||
| 		&.true { | ||||
| 			font-weight: bold; | ||||
| 		} | ||||
|  | ||||
| 		&.false { | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .string { | ||||
| @@ -78,7 +110,12 @@ export default defineComponent({ | ||||
| 		color: var(--codeNumber); | ||||
| 	} | ||||
|  | ||||
| 	> .array { | ||||
| 	> .array.empty { | ||||
| 		display: inline; | ||||
| 		opacity: 0.7; | ||||
| 	} | ||||
|  | ||||
| 	> .array:not(.empty) { | ||||
| 		display: inline; | ||||
|  | ||||
| 		> .element { | ||||
| @@ -87,13 +124,28 @@ export default defineComponent({ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .object { | ||||
| 	> .object.empty { | ||||
| 		display: inline; | ||||
| 		opacity: 0.7; | ||||
| 	} | ||||
|  | ||||
| 	> .object:not(.empty) { | ||||
| 		display: inline; | ||||
|  | ||||
| 		> .kv { | ||||
| 			display: block; | ||||
| 			padding-left: 16px; | ||||
|  | ||||
| 			> .toggle { | ||||
| 				width: 16px; | ||||
| 				color: var(--accent); | ||||
| 				visibility: hidden; | ||||
|  | ||||
| 				&.visible { | ||||
| 					visibility: visible; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .k { | ||||
| 				display: inline; | ||||
| 				margin-right: 8px; | ||||
|   | ||||
| @@ -4,26 +4,13 @@ | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import XValue from './object-view.value.vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XValue | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	setup(props) { | ||||
|  | ||||
| 	} | ||||
| }); | ||||
| const props = defineProps<{ | ||||
| 	value: Record<string, unknown>; | ||||
| }>(); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo