1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-24 07:06:26 +09:00

add: avater decoration multiple select layer

resolve #54
This commit is contained in:
Fairy-Phy 2023-11-19 05:04:43 +09:00 committed by NoriDev
parent 40b82ba8a1
commit 35cd4adf86
7 changed files with 83 additions and 27 deletions

View File

@ -1,5 +1,7 @@
---
_lang_: "English"
maxinumLayerError: "You cannot stack more than 6 layers. Please delete other layers."
layer: "Layer"
noteUpdatedAt: "Edited: {date} {time}"
editReaction: "Edit reactions"
removeReaction: "Remove reactions"

View File

@ -1,5 +1,7 @@
_lang_: "日本語"
maxinumLayerError: "6枚以上重ねることはできません。他のレイヤーを削除してください。"
layer: "レイヤー"
noteUpdatedAt: "編集済み: {date} {time}"
editReaction: "リアクションを編集"
removeReaction: "リアクションを削除"

View File

@ -1,5 +1,7 @@
---
_lang_: "日本語 (関西弁)"
maxinumLayerError: "6枚以上重ねられんで。他のレイヤーを削除してなー"
layer: "レイヤー"
headlineMisskey: "ノートでつながるネットワーク"
introMisskey: "ようお越しCherryPickは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀"
poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>CherryPick</b>のサーバーのひとつなんやで。"

View File

@ -137,7 +137,7 @@ export const paramDef = {
birthday: { ...birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
avatarDecorations: { type: 'array', maxItems: 1, items: {
avatarDecorations: { type: 'array', maxItems: 5, items: {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },

View File

@ -34,12 +34,24 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<img
v-if="showDecoration && (decoration || user.avatarDecorations.length > 0)"
v-if="showDecoration && !decoration && user.avatarDecorations.length > 0"
v-for="avatarDecoration in user.avatarDecorations"
:key="avatarDecoration.id"
:class="[$style.decoration]"
:src="decoration?.url ?? user.avatarDecorations[0].url"
:src="avatarDecoration.url"
:style="{
rotate: getDecorationAngle(),
scale: getDecorationScale(),
rotate: getDecorationAngle(avatarDecoration),
scale: getDecorationScale(avatarDecoration),
}"
alt=""
>
<img
v-else-if="showDecoration && decoration"
:class="[$style.decoration]"
:src="decoration?.url"
:style="{
rotate: getDecorationAngle(decoration),
scale: getDecorationScale(decoration),
}"
alt=""
>
@ -105,27 +117,13 @@ function onClick(ev: MouseEvent): void {
emit('click', ev);
}
function getDecorationAngle() {
let angle;
if (props.decoration) {
angle = props.decoration.angle ?? 0;
} else if (props.user.avatarDecorations.length > 0) {
angle = props.user.avatarDecorations[0].angle ?? 0;
} else {
angle = 0;
}
function getDecorationAngle(avatarDecoration) {
let angle = avatarDecoration.angle ?? 0;
return angle === 0 ? undefined : `${angle * 360}deg`;
}
function getDecorationScale() {
let scaleX;
if (props.decoration) {
scaleX = props.decoration.flipH ? -1 : 1;
} else if (props.user.avatarDecorations.length > 0) {
scaleX = props.user.avatarDecorations[0].flipH ? -1 : 1;
} else {
scaleX = 1;
}
function getDecorationScale(avatarDecoration) {
let scaleX = avatarDecoration.flipH ? -1 : 1;
return scaleX === 1 ? undefined : `${scaleX} 1`;
}

View File

@ -20,6 +20,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decoration="{ url: decoration.url, angle, flipH }" forceShowDecoration/>
</div>
<div class="_gaps_s">
<MkRadios v-model="insertLayer">
<template #label>{{ i18n.ts.layer }}</template>
<option value="0">1</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
</MkRadios>
<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>
@ -41,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef, ref, computed } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
@ -63,6 +72,18 @@ const emit = defineEmits<{
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const using = computed(() => $i.avatarDecorations.some(x => x.id === props.decoration.id));
const layerNum = (() => {
let result = -1;
$i.avatarDecorations.some((x, i) => {
if (x.id === props.decoration.id) {
result = i;
return true;
}
return false;
});
return result;
})();
const insertLayer = ref(layerNum === -1 ? String($i.avatarDecorations.length) : String(layerNum));
const angle = ref(using.value ? $i.avatarDecorations.find(x => x.id === props.decoration.id).angle ?? 0 : 0);
const flipH = ref(using.value ? $i.avatarDecorations.find(x => x.id === props.decoration.id).flipH ?? false : false);
@ -76,19 +97,22 @@ async function attach() {
angle: angle.value,
flipH: flipH.value,
};
const updatedDecorations = $i.avatarDecorations.toSpliced(layerNum, layerNum === -1 ? 0 : 1).toSpliced(Number(insertLayer.value), 0, decoration);
await os.apiWithDialog('i/update', {
avatarDecorations: [decoration],
avatarDecorations: updatedDecorations,
});
$i.avatarDecorations = [decoration];
$i.avatarDecorations = updatedDecorations;
dialog.value.close();
}
async function detach() {
if (layerNum === -1) return;
const deletedDecorations = $i.avatarDecorations.toSpliced(layerNum, 1);
await os.apiWithDialog('i/update', {
avatarDecorations: [],
avatarDecorations: deletedDecorations,
});
$i.avatarDecorations = [];
$i.avatarDecorations = deletedDecorations;
dialog.value.close();
}

View File

@ -97,6 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decoration="{ url: avatarDecoration.url }" forceShowDecoration/>
<i v-if="avatarDecoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => avatarDecoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.avatarDecorationLock" class="ti ti-lock"></i>
<span v-if="$i.avatarDecorations.some(x => x.id === avatarDecoration.id)" :class="$style.layerNum">{{ indexOfDecoration(v => v.id === avatarDecoration.id) + 1 }}</span>
</div>
</div>
</MkFolder>
@ -163,6 +164,18 @@ watch(() => profile, () => {
deep: true,
});
function indexOfDecoration(f) {
let result = -1;
$i.avatarDecorations.some((e, i) => {
if (f(e)) {
result = i;
return true;
}
return false;
});
return result;
}
const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
const fieldEditMode = ref(false);
@ -277,6 +290,14 @@ function changeBanner(ev) {
}
function openDecoration(avatarDecoration) {
if (indexOfDecoration(v => v.id === avatarDecoration.id) === -1 && $i.avatarDecorations.length >= 5) {
os.alert({
type: 'error',
title: i18n.ts.error,
text: i18n.ts.maxinumLayerError
});
return;
}
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration-dialog.vue')), {
decoration: avatarDecoration,
}, {}, 'closed');
@ -415,4 +436,11 @@ definePageMetadata({
bottom: 12px;
right: 12px;
}
.layerNum {
position: absolute;
left: 0;
top: 0;
margin: 10px;
}
</style>