enhance(frontend): tweak avatar decoration setting ui

This commit is contained in:
syuilo 2023-12-16 17:37:50 +09:00
parent c167f20643
commit 776eea736a
7 changed files with 68 additions and 43 deletions

1
locales/index.d.ts vendored
View File

@ -1183,6 +1183,7 @@ export interface Locale {
"remainingN": string; "remainingN": string;
"overwriteContentConfirm": string; "overwriteContentConfirm": string;
"seasonalScreenEffect": string; "seasonalScreenEffect": string;
"decorate": string;
"_announcement": { "_announcement": {
"forExistingUsers": string; "forExistingUsers": string;
"forExistingUsersDescription": string; "forExistingUsersDescription": string;

View File

@ -1180,6 +1180,7 @@ reloadRequiredToApplySettings: "設定の反映にはリロードが必要です
remainingN: "残り: {n}" remainingN: "残り: {n}"
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?" overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
seasonalScreenEffect: "季節に応じた画面の演出" seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
_announcement: _announcement:
forExistingUsers: "既存ユーザーのみ" forExistingUsers: "既存ユーザーのみ"

View File

@ -4,51 +4,56 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div v-if="!loading" class="_gaps"> <div>
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo> <div v-if="!loading" class="_gaps">
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s"> <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
<div>{{ i18n.ts.inUse }}</div>
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
<div>{{ i18n.ts.inUse }}</div>
<div :class="$style.decorations">
<XDecoration
v-for="(avatarDecoration, i) in $i.avatarDecorations"
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
:angle="avatarDecoration.angle"
:flipH="avatarDecoration.flipH"
:offsetX="avatarDecoration.offsetX"
:offsetY="avatarDecoration.offsetY"
:active="true"
@click="openDecoration(avatarDecoration, i)"
/>
</div>
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
</div>
<div :class="$style.decorations"> <div :class="$style.decorations">
<XDecoration <XDecoration
v-for="(avatarDecoration, i) in $i.avatarDecorations" v-for="avatarDecoration in avatarDecorations"
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)" :key="avatarDecoration.id"
:angle="avatarDecoration.angle" :decoration="avatarDecoration"
:flipH="avatarDecoration.flipH" @click="openDecoration(avatarDecoration)"
:offsetX="avatarDecoration.offsetX"
:offsetY="avatarDecoration.offsetY"
:active="true"
@click="openDecoration(avatarDecoration, i)"
/> />
</div> </div>
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
</div> </div>
<div v-else>
<div :class="$style.decorations"> <MkLoading/>
<XDecoration
v-for="avatarDecoration in avatarDecorations"
:key="avatarDecoration.id"
:decoration="avatarDecoration"
@click="openDecoration(avatarDecoration)"
/>
</div> </div>
</div> </div>
<div v-else>
<MkLoading/>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, defineAsyncComponent } from 'vue'; import { ref, defineAsyncComponent, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XDecoration from './profile.avatar-decoration.decoration.vue'; import XDecoration from './avatar-decoration.decoration.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const loading = ref(true); const loading = ref(true);
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]); const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
@ -59,7 +64,7 @@ os.api('get-avatar-decorations').then(_avatarDecorations => {
}); });
function openDecoration(avatarDecoration, index?: number) { function openDecoration(avatarDecoration, index?: number) {
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration.dialog.vue')), { os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {
decoration: avatarDecoration, decoration: avatarDecoration,
usingIndex: index, usingIndex: index,
}, { }, {
@ -115,9 +120,25 @@ function detachAllDecorations() {
$i.avatarDecorations = []; $i.avatarDecorations = [];
}); });
} }
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.avatarDecorations,
icon: 'ti ti-sparkles',
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.avatar {
display: inline-block;
width: 72px;
height: 72px;
margin: 16px auto;
}
.current { .current {
padding: 16px; padding: 16px;
border-radius: var(--radius); border-radius: var(--radius);

View File

@ -5,12 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="_panel">
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
</div>
<div :class="$style.avatarContainer"> <div :class="$style.avatarContainer">
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/> <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> <div class="_buttonsCenter">
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
</div>
</div> </div>
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
</div> </div>
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']"> <MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
@ -83,13 +88,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template> <template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
</FormSlot> </FormSlot>
<MkFolder>
<template #icon><i class="ti ti-sparkles"></i></template>
<template #label>{{ i18n.ts.avatarDecorations }}</template>
<XAvatarDecoration/>
</MkFolder>
<MkFolder> <MkFolder>
<template #label>{{ i18n.ts.advancedSettings }}</template> <template #label>{{ i18n.ts.advancedSettings }}</template>
@ -264,19 +262,19 @@ definePageMetadata({
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.avatarAndBanner { .banner {
position: relative; position: relative;
height: 130px;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
border: solid 1px var(--divider); border-bottom: solid 1px var(--divider);
border-radius: 10px;
overflow: clip; overflow: clip;
} }
.avatarContainer { .avatarContainer {
display: inline-block; margin-top: -50px;
padding-bottom: 16px;
text-align: center; text-align: center;
padding: 16px;
} }
.avatar { .avatar {

View File

@ -54,6 +54,10 @@ export const routes = [{
path: '/profile', path: '/profile',
name: 'profile', name: 'profile',
component: page(() => import('./pages/settings/profile.vue')), component: page(() => import('./pages/settings/profile.vue')),
}, {
path: '/avatar-decoration',
name: 'avatarDecoration',
component: page(() => import('./pages/settings/avatar-decoration.vue')),
}, { }, {
path: '/roles', path: '/roles',
name: 'roles', name: 'roles',