diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-21 18:38:07 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-21 18:38:07 +0900 |
| commit | 2c0a139da60ddf33e82353acbb985230c71c78da (patch) | |
| tree | b12d2762b332b7c885c5b210954053869618b62a /packages/frontend/src/pages | |
| parent | Update CHANGELOG.md (diff) | |
| download | misskey-2c0a139da60ddf33e82353acbb985230c71c78da.tar.gz misskey-2c0a139da60ddf33e82353acbb985230c71c78da.tar.bz2 misskey-2c0a139da60ddf33e82353acbb985230c71c78da.zip | |
feat: Avatar decoration (#12096)
* wip
* Update ja-JP.yml
* Update profile.vue
* .js
* Update home.test.ts
Diffstat (limited to 'packages/frontend/src/pages')
| -rw-r--r-- | packages/frontend/src/pages/admin/avatar-decorations.vue | 103 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/index.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/modlog.ModLog.vue | 12 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/profile.vue | 55 |
4 files changed, 173 insertions, 2 deletions
diff --git a/packages/frontend/src/pages/admin/avatar-decorations.vue b/packages/frontend/src/pages/admin/avatar-decorations.vue new file mode 100644 index 0000000000..b4007e6d20 --- /dev/null +++ b/packages/frontend/src/pages/admin/avatar-decorations.vue @@ -0,0 +1,103 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkStickyContainer> + <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> + <MkSpacer :contentMax="900"> + <div class="_gaps"> + <MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null"> + <template #label>{{ avatarDecoration.name }}</template> + <template #caption>{{ avatarDecoration.description }}</template> + + <div class="_gaps_m"> + <MkInput v-model="avatarDecoration.name"> + <template #label>{{ i18n.ts.name }}</template> + </MkInput> + <MkTextarea v-model="avatarDecoration.description"> + <template #label>{{ i18n.ts.description }}</template> + </MkTextarea> + <MkInput v-model="avatarDecoration.url"> + <template #label>{{ i18n.ts.imageUrl }}</template> + </MkInput> + <div class="buttons _buttons"> + <MkButton class="button" inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="avatarDecoration.id != null" class="button" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </div> + </MkFolder> + </div> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import XHeader from './_header_.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkRadios from '@/components/MkRadios.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkFolder from '@/components/MkFolder.vue'; + +let avatarDecorations: any[] = $ref([]); + +function add() { + avatarDecorations.unshift({ + _id: Math.random().toString(36), + id: null, + name: '', + description: '', + url: '', + }); +} + +function del(avatarDecoration) { + os.confirm({ + type: 'warning', + text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }), + }).then(({ canceled }) => { + if (canceled) return; + avatarDecorations = avatarDecorations.filter(x => x !== avatarDecoration); + os.api('admin/avatar-decorations/delete', avatarDecoration); + }); +} + +async function save(avatarDecoration) { + if (avatarDecoration.id == null) { + await os.apiWithDialog('admin/avatar-decorations/create', avatarDecoration); + load(); + } else { + os.apiWithDialog('admin/avatar-decorations/update', avatarDecoration); + } +} + +function load() { + os.api('admin/avatar-decorations/list').then(_avatarDecorations => { + avatarDecorations = _avatarDecorations; + }); +} + +load(); + +const headerActions = $computed(() => [{ + asFullButton: true, + icon: 'ti ti-plus', + text: i18n.ts.add, + handler: add, +}]); + +const headerTabs = $computed(() => []); + +definePageMetadata({ + title: i18n.ts.avatarDecorations, + icon: 'ti ti-sparkles', +}); +</script> diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index a508c20cf3..b304edbf57 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -116,6 +116,11 @@ const menuDef = $computed(() => [{ to: '/admin/emojis', active: currentPage?.route.name === 'emojis', }, { + icon: 'ti ti-sparkles', + text: i18n.ts.avatarDecorations, + to: '/admin/avatar-decorations', + active: currentPage?.route.name === 'avatarDecorations', + }, { icon: 'ti ti-whirl', text: i18n.ts.federation, to: '/admin/federation', diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 0af226f02e..bceefcf6c8 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label> <b :class="{ - [$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation'].includes(log.type), + [$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation', 'createAvatarDecoration'].includes(log.type), [$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword'].includes(log.type), - [$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd'].includes(log.type) + [$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd', 'deleteAvatarDecoration'].includes(log.type) }" >{{ i18n.ts._moderationLogTypes[log.type] }}</b> <span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> @@ -37,6 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span> <span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span> + <span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span> + <span v-else-if="log.type === 'updateAvatarDecoration'">: {{ log.info.before.name }}</span> + <span v-else-if="log.type === 'deleteAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span> </template> <template #icon> <MkAvatar :user="log.user" :class="$style.avatar"/> @@ -102,6 +105,11 @@ SPDX-License-Identifier: AGPL-3.0-only <CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/> </div> </template> + <template v-else-if="log.type === 'updateAvatarDecoration'"> + <div :class="$style.diff"> + <CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/> + </div> + </template> <details> <summary>raw</summary> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 5e4889f61c..c44a58d04a 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -84,6 +84,23 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSlot> <MkFolder> + <template #icon><i class="ti ti-sparkles"></i></template> + <template #label>{{ i18n.ts.avatarDecorations }}</template> + + <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-gap: 12px;"> + <div + v-for="avatarDecoration in avatarDecorations" + :key="avatarDecoration.id" + :class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]" + @click="toggleDecoration(avatarDecoration)" + > + <div :class="$style.avatarDecorationName">{{ avatarDecoration.name }}</div> + <MkAvatar style="width: 64px; height: 64px;" :user="$i" :decoration="avatarDecoration.url"/> + </div> + </div> + </MkFolder> + + <MkFolder> <template #label>{{ i18n.ts.advancedSettings }}</template> <div class="_gaps_m"> @@ -126,6 +143,7 @@ import MkInfo from '@/components/MkInfo.vue'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance')); +let avatarDecorations: any[] = $ref([]); const profile = reactive({ name: $i.name, @@ -146,6 +164,10 @@ watch(() => profile, () => { const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []); const fieldEditMode = ref(false); +os.api('get-avatar-decorations').then(_avatarDecorations => { + avatarDecorations = _avatarDecorations; +}); + function addField() { fields.value.push({ id: Math.random().toString(), @@ -244,6 +266,20 @@ function changeBanner(ev) { }); } +function toggleDecoration(avatarDecoration) { + if ($i.avatarDecorations.some(x => x.id === avatarDecoration.id)) { + os.apiWithDialog('i/update', { + avatarDecorations: [], + }); + $i.avatarDecorations = []; + } else { + os.apiWithDialog('i/update', { + avatarDecorations: [avatarDecoration.id], + }); + $i.avatarDecorations.push(avatarDecoration); + } +} + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); @@ -338,4 +374,23 @@ definePageMetadata({ .dragItemForm { flex-grow: 1; } + +.avatarDecoration { + cursor: pointer; + padding: 16px 16px 24px 16px; + border: solid 2px var(--divider); + border-radius: 8px; + text-align: center; +} + +.avatarDecorationActive { + border-color: var(--accent); +} + +.avatarDecorationName { + position: relative; + z-index: 10; + font-weight: bold; + margin-bottom: 16px; +} </style> |