diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 48c9e0261d..ef969e2321 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -149,3 +149,50 @@ export function inviteCode(isUsed = false, hasExpiration = false, isExpired = fa used: isUsed, } } + +export function role(params: { + id?: string, + name?: string, + color?: string | null, + iconUrl?: string | null, + description?: string, + isModerator?: boolean, + isAdministrator?: boolean, + displayOrder?: number, + createdAt?: string, + updatedAt?: string, + target?: 'manual' | 'conditional', + isPublic?: boolean, + isExplorable?: boolean, + asBadge?: boolean, + canEditMembersByModerator?: boolean, + usersCount?: number, +}): entities.Role { + const prefix = params.displayOrder ? params.displayOrder.toString().padStart(3, '0') + '-' : ''; + const genId = Math.random().toString(36).substring(10); + + return { + id: params.id ?? genId, + name: params.name ?? `${prefix}TestRole-${genId}`, + color: params.color ?? '#445566', + iconUrl: params.iconUrl ?? null, + description: params.description ?? '', + isModerator: params.isModerator ?? false, + isAdministrator: params.isAdministrator ?? false, + displayOrder: params.displayOrder ?? 0, + createdAt: params.createdAt ?? new Date().toISOString(), + updatedAt: params.updatedAt ?? new Date().toISOString(), + target: params.target ?? 'manual', + isPublic: params.isPublic ?? true, + isExplorable: params.isExplorable ?? true, + asBadge: params.asBadge ?? true, + canEditMembersByModerator: params.canEditMembersByModerator ?? false, + usersCount: params.usersCount ?? 10, + condFormula: { + id: '', + type: 'or', + values: [] + }, + policies: {}, + } +} diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 959785d879..6b22022f41 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -410,6 +410,7 @@ function toStories(component: string): Promise { glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkInviteCode.vue'), glob('src/components/MkTagItem.vue'), + glob('src/components/MkRoleSelectDialog.vue'), glob('src/pages/user/home.vue'), ]); const components = globs.flat(); diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 40e67fb4e0..6302808018 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -276,17 +276,19 @@ const align = () => { const onOpened = () => { emit('opened'); - // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する - const el = content.value!.children[0]; - el.addEventListener('mousedown', ev => { - contentClicking = true; - window.addEventListener('mouseup', ev => { - // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ - window.setTimeout(() => { - contentClicking = false; - }, 100); - }, { passive: true, once: true }); - }, { passive: true }); + nextTick(() => { + // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する + const el = content.value!.children[0]; + el.addEventListener('mousedown', ev => { + contentClicking = true; + window.addEventListener('mouseup', ev => { + // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ + window.setTimeout(() => { + contentClicking = false; + }, 100); + }, { passive: true, once: true }); + }, { passive: true }); + }); }; const alignObserver = new ResizeObserver((entries, observer) => { diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts new file mode 100644 index 0000000000..5ebd30c6be --- /dev/null +++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { http, HttpResponse } from 'msw'; +import { role } from '../../.storybook/fakes.js'; +import { commonHandlers } from '../../.storybook/mocks.js'; +import MkRoleSelectDialog from '@/components/MkRoleSelectDialog.vue'; + +const roles = [ + role({ displayOrder: 1 }), role({ displayOrder: 1 }), role({ displayOrder: 1 }), role({ displayOrder: 1 }), + role({ displayOrder: 2 }), role({ displayOrder: 2 }), role({ displayOrder: 3 }), role({ displayOrder: 3 }), + role({ displayOrder: 4 }), role({ displayOrder: 5 }), role({ displayOrder: 6 }), role({ displayOrder: 7 }), +]; + +export const Default = { + render(args) { + return { + components: { + MkRoleSelectDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + initialRoleIds: undefined, + infoMessage: undefined, + title: undefined, + publicOnly: true, + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/admin/roles/list', ({ params }) => { + return HttpResponse.json(roles); + }), + ], + }, + }, + decorators: [() => ({ + template: '
', + })], +} satisfies StoryObj; + +export const InitialIds = { + ...Default, + args: { + ...Default.args, + initialRoleIds: [roles[0].id, roles[1].id, roles[4].id, roles[6].id], + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue index ebafbe5e5b..a787b89983 100644 --- a/packages/frontend/src/components/MkRoleSelectDialog.vue +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -11,23 +11,21 @@ -
+
{{ i18n.ts.add }}
-
-
-
- - - -
-
-
- 何も選択されていません +
+
+ + +
+
+ 何も選択されていません +
{{ infoMessage }} @@ -134,10 +132,18 @@ fetchRoles();