fix(frontend): 一部のグリッドのページネーションが正しく表示されない問題を修正 (#16147)

* fix(frontend): 一部のグリッドのページネーションが正しく表示されない問題を修正

* update changelog

* fix
This commit is contained in:
かっこかり 2025-06-03 17:20:11 +09:00 committed by GitHub
parent 5537558fa7
commit 810a609df4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 130 additions and 126 deletions

View File

@ -5,6 +5,7 @@
### Client ### Client
- Fix: ドライブファイルの選択が不安定な問題を修正 - Fix: ドライブファイルの選択が不安定な問題を修正
- Fix: コントロールパネルのファイル欄などのデザインが崩れている問題を修正
### Server ### Server
- -

View File

@ -5,33 +5,35 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div> <div>
<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }"> <MkPagination v-slot="{ items }" :pagination="pagination">
<MkA <div :class="[$style.fileList, { [$style.grid]: viewMode === 'grid', [$style.list]: viewMode === 'list', '_gaps_s': viewMode === 'list' }]">
v-for="file in (items as Misskey.entities.DriveFile[])" <MkA
:key="file.id" v-for="file in items"
v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`" :key="file.id"
:to="`/admin/file/${file.id}`" v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`"
class="file _button" :to="`/admin/file/${file.id}`"
> :class="[$style.file, '_button']"
<div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div> >
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/> <div v-if="file.isSensitive" :class="$style.sensitiveLabel">{{ i18n.ts.sensitive }}</div>
<div v-if="viewMode === 'list'" class="body"> <MkDriveFileThumbnail :class="$style.thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/>
<div> <div v-if="viewMode === 'list'" :class="$style.body">
<small style="opacity: 0.7;">{{ file.name }}</small> <div>
<small style="opacity: 0.7;">{{ file.name }}</small>
</div>
<div>
<MkAcct v-if="file.user" :user="file.user"/>
<div v-else>{{ i18n.ts.system }}</div>
</div>
<div>
<span style="margin-right: 1em;">{{ file.type }}</span>
<span>{{ bytes(file.size) }}</span>
</div>
<div>
<span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
</div>
</div> </div>
<div> </MkA>
<MkAcct v-if="file.user" :user="file.user"/> </div>
<div v-else>{{ i18n.ts.system }}</div>
</div>
<div>
<span style="margin-right: 1em;">{{ file.type }}</span>
<span>{{ bytes(file.size) }}</span>
</div>
<div>
<span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
</div>
</div>
</MkA>
</MkPagination> </MkPagination>
</div> </div>
</template> </template>
@ -43,76 +45,76 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js'; import { dateString } from '@/filters/date.js';
import type { PagingCtx } from '@/composables/use-pagination.js';
const props = defineProps<{ defineProps<{
pagination: any; pagination: PagingCtx<'admin/drive/files'>;
viewMode: 'grid' | 'list'; viewMode: 'grid' | 'list';
}>(); }>();
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
@keyframes sensitive-blink { @keyframes sensitive-blink {
0% { opacity: 1; } 0% { opacity: 1; }
50% { opacity: 0; } 50% { opacity: 0; }
} }
.urempief { .list {
&.list { > .file {
> .file { display: flex;
display: flex; width: 100%;
width: 100%; height: auto;
box-sizing: border-box; box-sizing: border-box;
text-align: left; text-align: left;
align-items: center; align-items: center;
&:hover {
color: var(--MI_THEME-accent);
}
> .thumbnail {
width: 128px;
height: 128px;
}
> .body {
margin-left: 0.3em;
padding: 8px;
flex: 1;
@media (max-width: 500px) {
font-size: 14px;
}
}
}
} }
&.grid { > .file:hover {
display: grid; color: var(--MI_THEME-accent);
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
grid-gap: 12px;
> .file { > .file > .thumbnail {
position: relative; width: 128px;
aspect-ratio: 1; height: 128px;
}
> .thumbnail { > .file > .body {
width: 100%; margin-left: 0.3em;
height: 100%; padding: 8px;
} flex: 1;
> .sensitive-label { @media (max-width: 500px) {
position: absolute; font-size: 14px;
z-index: 10;
top: 8px;
left: 8px;
padding: 2px 4px;
background: #ff0000bf;
color: #fff;
border-radius: 4px;
font-size: 85%;
animation: sensitive-blink 1s infinite;
}
} }
} }
} }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
grid-gap: 12px;
> .file {
position: relative;
aspect-ratio: 1;
}
.thumbnail {
width: 100%;
height: 100%;
}
}
.sensitiveLabel {
position: absolute;
z-index: 10;
top: 8px;
left: 8px;
padding: 2px 4px;
background: #ff0000bf;
color: #fff;
border-radius: 4px;
font-size: 85%;
animation: sensitive-blink 1s infinite;
}
</style> </style>

View File

@ -34,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
@ -41,12 +42,13 @@ import * as os from '@/os.js';
import { lookupFile } from '@/utility/admin-lookup.js'; import { lookupFile } from '@/utility/admin-lookup.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import type { PagingCtx } from '@/composables/use-pagination.js';
const origin = ref('local'); const origin = ref<Misskey.entities.AdminDriveFilesRequest['origin']>('local');
const type = ref<string | null>(null); const type = ref<string | null>(null);
const searchHost = ref(''); const searchHost = ref('');
const userId = ref(''); const userId = ref('');
const viewMode = ref('grid'); const viewMode = ref<'grid' | 'list'>('grid');
const pagination = { const pagination = {
endpoint: 'admin/drive/files' as const, endpoint: 'admin/drive/files' as const,
limit: 10, limit: 10,
@ -56,7 +58,7 @@ const pagination = {
origin: origin.value, origin: origin.value,
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null, hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
})), })),
}; } satisfies PagingCtx<'admin/drive/files'>;
function clear() { function clear() {
os.confirm({ os.confirm({

View File

@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true"> <PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
<div v-if="instance" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;"> <div v-if="instance" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<div v-if="tab === 'overview'" class="_gaps_m"> <div v-if="tab === 'overview'" class="_gaps_m">
<div class="fnfelxur"> <div :class="$style.faviconAndName">
<img :src="faviconUrl" alt="" class="icon"/> <img :src="faviconUrl" alt="" :class="$style.icon"/>
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span> <span :class="$style.name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
</div> </div>
<div style="display: flex; flex-direction: column; gap: 1em;"> <div style="display: flex; flex-direction: column; gap: 1em;">
<MkKeyValue :copy="host" oneline> <MkKeyValue :copy="host" oneline>
@ -90,8 +90,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection> </FormSection>
</div> </div>
<div v-else-if="tab === 'chart'" class="_gaps_m"> <div v-else-if="tab === 'chart'" class="_gaps_m">
<div class="cmhjzshl"> <div>
<div class="selects"> <div :class="$style.selects">
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;"> <MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option> <option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option> <option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
@ -106,19 +106,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option> <option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
</MkSelect> </MkSelect>
</div> </div>
<div class="charts"> <div>
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div> <div :class="$style.label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> <MkChart :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div> <div :class="$style.label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> <MkChart :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="tab === 'users'" class="_gaps_m"> <div v-else-if="tab === 'users'" class="_gaps_m">
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;"> <MkPagination v-slot="{ items }" :pagination="usersPagination">
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`"> <div :class="$style.users">
<MkUserCardMini :user="user"/> <MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${user.updatedAt ? dateString(user.updatedAt) : 'unknown'}`" :to="`/admin/user/${user.id}`">
</MkA> <MkUserCardMini :user="user"/>
</MkA>
</div>
</MkPagination> </MkPagination>
</div> </div>
<div v-else-if="tab === 'raw'" class="_gaps_m"> <div v-else-if="tab === 'raw'" class="_gaps_m">
@ -180,7 +182,7 @@ const usersPagination = {
hostname: props.host, hostname: props.host,
}, },
offsetMode: true, offsetMode: true,
} satisfies PagingCtx; } satisfies PagingCtx<'admin/show-users' | 'users'>;
if (iAmModerator) { if (iAmModerator) {
watch(moderationNote, async () => { watch(moderationNote, async () => {
@ -281,7 +283,7 @@ const headerTabs = computed(() => [{
key: 'overview', key: 'overview',
title: i18n.ts.overview, title: i18n.ts.overview,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
}, { }, ...(iAmModerator ? [{
key: 'chart', key: 'chart',
title: i18n.ts.charts, title: i18n.ts.charts,
icon: 'ti ti-chart-line', icon: 'ti ti-chart-line',
@ -289,7 +291,7 @@ const headerTabs = computed(() => [{
key: 'users', key: 'users',
title: i18n.ts.users, title: i18n.ts.users,
icon: 'ti ti-users', icon: 'ti ti-users',
}, { }] : []), {
key: 'raw', key: 'raw',
title: 'Raw', title: 'Raw',
icon: 'ti ti-code', icon: 'ti ti-code',
@ -301,34 +303,31 @@ definePage(() => ({
})); }));
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.fnfelxur { .faviconAndName {
display: flex; display: flex;
align-items: center; align-items: center;
> .icon {
display: block;
margin: 0 16px 0 0;
height: 64px;
border-radius: 8px;
}
> .name {
word-break: break-all;
}
} }
.icon {
.cmhjzshl { display: block;
> .selects { margin: 0 16px 0 0;
display: flex; height: 64px;
margin: 0 0 16px 0; border-radius: 8px;
} }
.name {
> .charts { word-break: break-all;
> .label { }
margin-bottom: 12px; .selects {
font-weight: bold; display: flex;
} margin: 0 0 16px 0;
} }
.label {
margin-bottom: 12px;
font-weight: bold;
}
.users {
display: grid;
grid-template-columns: repeat(auto-fill,minmax(270px,1fr));
grid-gap: 12px;
} }
</style> </style>