summaryrefslogtreecommitdiff
path: root/packages/client/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-06-29 15:41:06 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-06-29 15:41:06 +0900
commit4fd386c3dc5346576d52c9baaa29574d07dc6d86 (patch)
tree0dc451b304a97368f33ff09b0be63fad6d4a72ca /packages/client/src/components
parentchore(client): tweak style (diff)
downloadsharkey-4fd386c3dc5346576d52c9baaa29574d07dc6d86.tar.gz
sharkey-4fd386c3dc5346576d52c9baaa29574d07dc6d86.tar.bz2
sharkey-4fd386c3dc5346576d52c9baaa29574d07dc6d86.zip
chore(client): tweak client
Diffstat (limited to 'packages/client/src/components')
-rw-r--r--packages/client/src/components/instance-stats.vue221
-rw-r--r--packages/client/src/components/object-view.value.vue106
-rw-r--r--packages/client/src/components/object-view.vue23
3 files changed, 248 insertions, 102 deletions
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>
diff --git a/packages/client/src/components/object-view.value.vue b/packages/client/src/components/object-view.value.vue
index 6f388636dd..0c7230d783 100644
--- a/packages/client/src/components/object-view.value.vue
+++ b/packages/client/src/components/object-view.value.vue
@@ -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;
diff --git a/packages/client/src/components/object-view.vue b/packages/client/src/components/object-view.vue
index e9db96de8c..db66049fce 100644
--- a/packages/client/src/components/object-view.vue
+++ b/packages/client/src/components/object-view.vue
@@ -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>