diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-05-06 14:41:31 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-06 14:41:31 +0900 |
| commit | 8959bfa1c0b558888aa7da207f8166092c51a353 (patch) | |
| tree | 6724fbcd6be6831878d4dfa6bbddcffc6052b81d /packages/frontend/src/components | |
| parent | chore(deps): sharpを固定 (#15957) (diff) | |
| download | misskey-8959bfa1c0b558888aa7da207f8166092c51a353.tar.gz misskey-8959bfa1c0b558888aa7da207f8166092c51a353.tar.bz2 misskey-8959bfa1c0b558888aa7da207f8166092c51a353.zip | |
refactor(frontend): 空/エラー結果表示をコンポーネント化 (#15963)
* wip
* wip
* wip
* wip
* wip
* Update MkResult.vue
* Add storybook story for MkResult (#15964)
* Update MkResult.vue
---------
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src/components')
13 files changed, 114 insertions, 54 deletions
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index fdb7d2a1c4..d0b50f04f2 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -5,12 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkPagination :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.notFound }}</div> - </div> - </template> + <template #empty><MkResult type="empty"/></template> <template #default="{ items }"> <MkChannelPreview v-for="item in items" :key="item.id" class="_margin" :channel="extractor(item)"/> @@ -23,7 +18,6 @@ import type { Paging } from '@/components/MkPagination.vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkChatHistories.vue b/packages/frontend/src/components/MkChatHistories.vue index c508ea8451..b33ed428c7 100644 --- a/packages/frontend/src/components/MkChatHistories.vue +++ b/packages/frontend/src/components/MkChatHistories.vue @@ -28,9 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkA> </div> -<div v-if="!initializing && history.length == 0" class="_fullinfo"> - <div>{{ i18n.ts._chat.noHistory }}</div> -</div> +<MkResult v-if="!initializing && history.length == 0" type="empty" :text="i18n.ts._chat.noHistory"/> <MkLoading v-if="initializing"/> </template> diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 0884cdc016..6ac4441cac 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -62,10 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only /> </template> </div> - <div v-else class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.nothing }}</div> - </div> + <MkResult v-else type="empty"/> </div> </MkModalWindow> </template> @@ -83,7 +80,6 @@ import XFile from './MkFormDialog.file.vue'; import type { Form } from '@/utility/form.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = defineProps<{ title: string; diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index 9d862a4eac..509099e0b9 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -5,12 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.noNotes }}</div> - </div> - </template> + <template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template> <template #default="{ items: notes }"> <div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: pagination.reversed }]"> @@ -34,7 +29,6 @@ import type { Paging } from '@/components/MkPagination.vue'; import MkNote from '@/components/MkNote.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 9672efca0a..21104b41df 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -11,7 +11,6 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> - <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> <MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/> <img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/> <div @@ -176,7 +175,6 @@ import { userPage } from '@/filters/user.js'; import { i18n } from '@/i18n.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { ensureSignin } from '@/i.js'; -import { infoImageUrl } from '@/instance.js'; const $i = ensureSignin(); diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 308a077bd9..3c88b8af0d 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -6,12 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reload()"> <MkPagination ref="pagingComponent" :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.noNotifications }}</div> - </div> - </template> + <template #empty><MkResult type="empty" :text="i18n.ts.noNotifications"/></template> <template #default="{ items: notifications }"> <component @@ -42,7 +37,6 @@ import XNotification from '@/components/MkNotification.vue'; import MkNote from '@/components/MkNote.vue'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 9adc3d98da..54da5a889d 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -16,12 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkError v-else-if="error" @retry="init()"/> <div v-else-if="empty" key="_empty_"> - <slot name="empty"> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.nothing }}</div> - </div> - </slot> + <slot name="empty"><MkResult type="empty"/></slot> </div> <div v-else ref="rootEl" class="_gaps"> @@ -88,7 +83,6 @@ function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): M </script> <script lang="ts" setup> -import { infoImageUrl } from '@/instance.js'; import MkButton from '@/components/MkButton.vue'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index e2c261787b..6a265aa836 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -6,12 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reloadTimeline()"> <MkPagination v-if="paginationQuery" ref="pagingComponent" :pagination="paginationQuery" @queue="emit('queue', $event)"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.noNotes }}</div> - </div> - </template> + <template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template> <template #default="{ items: notes }"> <component @@ -53,7 +48,6 @@ import { prefer } from '@/preferences.js'; import MkNote from '@/components/MkNote.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role'; diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 0d1ffd715f..90087cb000 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -5,12 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkPagination :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" draggable="false"/> - <div>{{ i18n.ts.noUsers }}</div> - </div> - </template> + <template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template> <template #default="{ items }"> <div :class="$style.root"> @@ -25,7 +20,6 @@ import type { Paging } from '@/components/MkPagination.vue'; import MkUserInfo from '@/components/MkUserInfo.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue index 95ed255189..bc3a282e40 100644 --- a/packages/frontend/src/components/global/MkError.vue +++ b/packages/frontend/src/components/global/MkError.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> <div :class="$style.root"> - <img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/> + <img v-if="instance.serverErrorImageUrl" :class="$style.img" :src="instance.serverErrorImageUrl" draggable="false"/> <p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> <MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton> </div> @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; -import { serverErrorImageUrl } from '@/instance.js'; +import { instance } from '@/instance.js'; const emit = defineEmits<{ (ev: 'retry'): void; diff --git a/packages/frontend/src/components/global/MkResult.stories.impl.ts b/packages/frontend/src/components/global/MkResult.stories.impl.ts new file mode 100644 index 0000000000..05f8c9069b --- /dev/null +++ b/packages/frontend/src/components/global/MkResult.stories.impl.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MkResult from './MkResult.vue'; +import type { StoryObj } from '@storybook/vue3'; +export const Default = { + render(args) { + return { + components: { + MkResult, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkResult v-bind="props" />', + }; + }, + args: { + type: 'empty', + text: 'Lorem Ipsum', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkResult>; +export const emptyWithNoText = { + ...Default, + args: { + ...Default.args, + text: undefined, + }, +} satisfies StoryObj<typeof MkResult>; +export const notFound = { + ...Default, + args: { + ...Default.args, + type: 'notFound', + }, +} satisfies StoryObj<typeof MkResult>; +export const errorType = { + ...Default, + args: { + ...Default.args, + type: 'error', + }, +} satisfies StoryObj<typeof MkResult>; diff --git a/packages/frontend/src/components/global/MkResult.vue b/packages/frontend/src/components/global/MkResult.vue new file mode 100644 index 0000000000..51cf8a860a --- /dev/null +++ b/packages/frontend/src/components/global/MkResult.vue @@ -0,0 +1,44 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.root]" class="_gaps"> + <img v-if="type === 'empty' && instance.infoImageUrl" :src="instance.infoImageUrl" draggable="false" :class="$style.img"/> + <i v-else-if="type === 'empty'" class="ti ti-info-circle" :class="$style.icon"></i> + <div>{{ props.text ?? (type === 'empty' ? i18n.ts.nothing : type === 'notFound' ? i18n.ts.notFound : null) }}</div> + <slot></slot> +</div> +</template> + +<script lang="ts" setup> +import {} from 'vue'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; + +const props = defineProps<{ + type: 'empty' | 'notFound' | 'error'; + text?: string; +}>(); +</script> + +<style lang="scss" module> +.root { + position: relative; + text-align: center; + padding: 32px; +} + +.img { + vertical-align: bottom; + height: 128px; + margin-bottom: 16px; + border-radius: 16px; +} + +.icon { + font-size: 24px; + margin: 0 auto; +} +</style> diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index ec6ea7c569..33d3532c1d 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -24,6 +24,7 @@ import MkAd from './global/MkAd.vue'; import MkPageHeader from './global/MkPageHeader.vue'; import MkStickyContainer from './global/MkStickyContainer.vue'; import MkLazy from './global/MkLazy.vue'; +import MkResult from './global/MkResult.vue'; import PageWithHeader from './global/PageWithHeader.vue'; import PageWithAnimBg from './global/PageWithAnimBg.vue'; import SearchMarker from './global/SearchMarker.vue'; @@ -61,6 +62,7 @@ export const components = { MkPageHeader: MkPageHeader, MkStickyContainer: MkStickyContainer, MkLazy: MkLazy, + MkResult: MkResult, PageWithHeader: PageWithHeader, PageWithAnimBg: PageWithAnimBg, SearchMarker: SearchMarker, @@ -92,6 +94,7 @@ declare module '@vue/runtime-core' { MkPageHeader: typeof MkPageHeader; MkStickyContainer: typeof MkStickyContainer; MkLazy: typeof MkLazy; + MkResult: typeof MkResult; PageWithHeader: typeof PageWithHeader; PageWithAnimBg: typeof PageWithAnimBg; SearchMarker: typeof SearchMarker; |