アイコンデコレーションのカテゴリ

This commit is contained in:
mattyatea 2024-01-03 01:25:54 +09:00
parent 1c108927ea
commit 4cdf048a7c
7 changed files with 228 additions and 49 deletions

View file

@ -8,41 +8,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><MkPageHeader v-model:tab="tab" :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 :class="$style.decorations">
<XDecoration
v-for="avatarDecoration in avatarDecorations"
:key="avatarDecoration.id"
:decoration="avatarDecoration"
@click="openDecorationEdit(avatarDecoration)"
/>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { ref, computed, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.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';
import XDecoration from '@/pages/settings/avatar-decoration.decoration.vue';
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
@ -53,27 +38,27 @@ function add() {
name: '',
description: '',
url: '',
category: '',
});
}
function del(avatarDecoration) {
os.confirm({
type: 'warning',
text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
}).then(({ canceled }) => {
if (canceled) return;
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
os.api('admin/avatar-decorations/delete', avatarDecoration);
function openDecorationEdit(avatarDecoration) {
os.popup(defineAsyncComponent(() => import('@/components/MkAvatarDecoEditDialog.vue')), {
avatarDecoration: avatarDecoration,
}, {
del: () => {
window.location.reload();
},
});
}
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 openDecorationCreate() {
os.popup(defineAsyncComponent(() => import('@/components/MkAvatarDecoEditDialog.vue')), {
}, {
del: result => {
window.location.reload();
},
});
}
function load() {
@ -88,7 +73,7 @@ const headerActions = computed(() => [{
asFullButton: true,
icon: 'ti ti-plus',
text: i18n.ts.add,
handler: add,
handler: openDecorationCreate,
}]);
const headerTabs = computed(() => []);
@ -98,3 +83,10 @@ definePageMetadata({
icon: 'ti ti-sparkles',
});
</script>
<style module>
.decorations {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
grid-gap: 12px;
}
</style>

View file

@ -20,6 +20,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decorations="decorationsForPreview" forceShowDecoration/>
</div>
<div class="_gaps_s">
{{ i18n.ts.description }}
<p style="white-space: pre-wrap;">{{ decoration.description }}</p>
<MkRange v-model="angle" continuousUpdate :min="-0.5" :max="0.5" :step="0.025" :textConverter="(v) => `${Math.floor(v * 360)}°`">
<template #label>{{ i18n.ts.angle }}</template>
</MkRange>
@ -59,6 +61,7 @@ const props = defineProps<{
id: string;
url: string;
name: string;
description: string;
};
}>();

View file

@ -29,13 +29,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
</div>
<div :class="$style.decorations">
<XDecoration
v-for="avatarDecoration in avatarDecorations"
:key="avatarDecoration.id"
:decoration="avatarDecoration"
@click="openDecoration(avatarDecoration)"
/>
<div v-for="category in categories">
<MkFoldableSection>
<template #header> {{ (category !== '') ? category : i18n.ts.other }}</template>
<div :class="$style.decorations">
<div v-for="avatarDecoration in avatarDecorations.filter(ad => ad.category === category)">
<XDecoration
:key="avatarDecoration.id"
:decoration="avatarDecoration"
@click="openDecoration(avatarDecoration)"
/>
</div>
</div>
</MkFoldableSection>
</div>
</div>
<div v-else>
@ -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<Misskey.entities.GetAvatarDecorationsResponse>([]);
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse & { category:string }>([]);
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')), {