forked from mirrors/misskey
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>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
44
packages/frontend/src/components/global/MkResult.vue
Normal file
44
packages/frontend/src/components/global/MkResult.vue
Normal file
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user