diff --git a/packages/backend/src/models/AvatarDecoration.ts b/packages/backend/src/models/AvatarDecoration.ts index 08ebbdeac1..f658663e68 100644 --- a/packages/backend/src/models/AvatarDecoration.ts +++ b/packages/backend/src/models/AvatarDecoration.ts @@ -31,6 +31,11 @@ export class MiAvatarDecoration { }) public description: string; + @Column('varchar', { + length: 256, + }) + public category: string; + // TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする @Column('varchar', { array: true, length: 128, default: '{}', diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts index 6211345f96..3d424c68de 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts @@ -30,6 +30,7 @@ export const paramDef = { roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: { type: 'string', } }, + category: { type: 'string', nullable: true }, }, required: ['id'], } as const; @@ -45,6 +46,7 @@ export default class extends Endpoint { // eslint- description: ps.description, url: ps.url, roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration, + category: ps.category ?? '', }, me); }); } diff --git a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts index dbe1626149..028d647393 100644 --- a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts +++ b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts @@ -75,6 +75,7 @@ export default class extends Endpoint { // eslint- name: decoration.name, description: decoration.description, url: decoration.url, + category: decoration.category, roleIdsThatCanBeUsedThisDecoration: decoration.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(role => role.id === roleId)), })); }); diff --git a/packages/frontend/src/components/MkAvatarDecoEditDialog.vue b/packages/frontend/src/components/MkAvatarDecoEditDialog.vue new file mode 100644 index 0000000000..9ef5be1df2 --- /dev/null +++ b/packages/frontend/src/components/MkAvatarDecoEditDialog.vue @@ -0,0 +1,164 @@ + + + + + + + diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue index 87964ac697..eae411fd0d 100644 --- a/packages/frontend/src/pages/avatar-decorations.vue +++ b/packages/frontend/src/pages/avatar-decorations.vue @@ -8,41 +8,26 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - -
- - - - - - - - - -
- {{ i18n.ts.save }} - {{ i18n.ts.delete }} -
-
-
+
+ +
+ diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue index 329ab4d47a..544f1dc9ac 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue @@ -20,6 +20,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+ {{ i18n.ts.description }} +

{{ decoration.description }}

@@ -59,6 +61,7 @@ const props = defineProps<{ id: string; url: string; name: string; + description: string; }; }>(); diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index 6551fc917e..34920e1ae3 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -29,13 +29,19 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.detachAll }}
-
- +
+ + +
+
+ +
+
+
@@ -54,14 +60,20 @@ import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import MkInfo from '@/components/MkInfo.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkFoldableSection from '@/components/MkFoldableSection.vue'; const loading = ref(true); -const avatarDecorations = ref([]); +const avatarDecorations = ref([]); os.api('get-avatar-decorations').then(_avatarDecorations => { avatarDecorations.value = _avatarDecorations; loading.value = false; }); +const categories = computed(() => { + const allCategories = avatarDecorations.value.map(ad => ad.category); + const uniqueCategories = [...new Set(allCategories)]; + return uniqueCategories.sort(); +}); function openDecoration(avatarDecoration, index?: number) { os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {