enhance: アイコンデコレーション管理画面の改善
This commit is contained in:
@@ -5,92 +5,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<template #header><MkPageHeader :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="$style.editorRoot">
|
||||
<div :class="$style.editorWrapper">
|
||||
<div :class="$style.preview">
|
||||
<div :class="[$style.previewItem, $style.light]">
|
||||
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/>
|
||||
</div>
|
||||
<div :class="[$style.previewItem, $style.dark]">
|
||||
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<MkButton inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton v-if="avatarDecoration.id != null" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.decorations">
|
||||
<div
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
v-panel
|
||||
:class="$style.decoration"
|
||||
@click="edit(avatarDecoration)"
|
||||
>
|
||||
<div :class="$style.decorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
|
||||
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: avatarDecoration.url }]" forceShowDecoration/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</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 { signinRequired } from '@/account.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
function add() {
|
||||
avatarDecorations.value.unshift({
|
||||
_id: Math.random().toString(36),
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
url: '',
|
||||
});
|
||||
}
|
||||
|
||||
function del(avatarDecoration) {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
|
||||
misskeyApi('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);
|
||||
}
|
||||
}
|
||||
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
|
||||
|
||||
function load() {
|
||||
misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => {
|
||||
@@ -100,6 +46,37 @@ function load() {
|
||||
|
||||
load();
|
||||
|
||||
async function add(ev: MouseEvent) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.created) {
|
||||
avatarDecorations.value.unshift(result.created);
|
||||
}
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function edit(avatarDecoration) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
|
||||
avatarDecoration: avatarDecoration,
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.updated) {
|
||||
const index = avatarDecorations.value.findIndex(x => x.id === avatarDecoration.id);
|
||||
avatarDecorations.value[index] = {
|
||||
...avatarDecorations.value[index],
|
||||
...result.updated,
|
||||
};
|
||||
} else if (result.deleted) {
|
||||
avatarDecorations.value = avatarDecorations.value.filter(x => x.id !== avatarDecoration.id);
|
||||
}
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
asFullButton: true,
|
||||
icon: 'ti ti-plus',
|
||||
@@ -116,53 +93,26 @@ definePageMetadata(() => ({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.editorRoot {
|
||||
container: editor / inline-size;
|
||||
}
|
||||
|
||||
.editorWrapper {
|
||||
.decorations {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
gap: var(--MI-margin);
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
grid-gap: 12px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: var(--MI-margin);
|
||||
.decoration {
|
||||
cursor: pointer;
|
||||
padding: 16px 16px 28px 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-size: 90%;
|
||||
overflow: clip;
|
||||
contain: content;
|
||||
}
|
||||
|
||||
.previewItem {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 160px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--MI-radius);
|
||||
|
||||
&.light {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
@container editor (min-width: 600px) {
|
||||
.editorWrapper {
|
||||
grid-template-columns: 200px 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: calc(var(--MI-margin) * 2);
|
||||
}
|
||||
|
||||
.preview {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.decorationName {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user