enhance: アイコンデコレーションの位置を微調整できるように

This commit is contained in:
syuilo 2023-12-14 20:58:08 +09:00
parent 239507d7d6
commit 417852779f
8 changed files with 45 additions and 3 deletions

View File

@ -31,6 +31,7 @@
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83) - Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
- Feat: TL上からートが見えなくなるワードミュートであるハードミュートを追加 - Feat: TL上からートが見えなくなるワードミュートであるハードミュートを追加
- Enhance: アイコンデコレーションを複数設定できるように - Enhance: アイコンデコレーションを複数設定できるように
- Enhance: アイコンデコレーションの位置を微調整できるように
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正 - Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
### Client ### Client

View File

@ -362,6 +362,8 @@ export class UserEntityService implements OnModuleInit {
id: ud.id, id: ud.id,
angle: ud.angle || undefined, angle: ud.angle || undefined,
flipH: ud.flipH || undefined, flipH: ud.flipH || undefined,
offsetX: ud.offsetX || undefined,
offsetY: ud.offsetY || undefined,
url: decorations.find(d => d.id === ud.id)!.url, url: decorations.find(d => d.id === ud.id)!.url,
}))) : [], }))) : [],
isBot: user.isBot, isBot: user.isBot,

View File

@ -143,8 +143,10 @@ export class MiUser {
}) })
public avatarDecorations: { public avatarDecorations: {
id: string; id: string;
angle: number; angle?: number;
flipH: boolean; flipH?: boolean;
offsetX?: number;
offsetY?: number;
}[]; }[];
@Index() @Index()

View File

@ -143,6 +143,8 @@ export const paramDef = {
id: { type: 'string', format: 'misskey:id' }, id: { type: 'string', format: 'misskey:id' },
angle: { type: 'number', nullable: true, maximum: 0.5, minimum: -0.5 }, angle: { type: 'number', nullable: true, maximum: 0.5, minimum: -0.5 },
flipH: { type: 'boolean', nullable: true }, flipH: { type: 'boolean', nullable: true },
offsetX: { type: 'number', nullable: true, maximum: 0.25, minimum: -0.25 },
offsetY: { type: 'number', nullable: true, maximum: 0.25, minimum: -0.25 },
}, },
required: ['id'], required: ['id'],
} }, } },
@ -341,6 +343,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: d.id, id: d.id,
angle: d.angle ?? 0, angle: d.angle ?? 0,
flipH: d.flipH ?? false, flipH: d.flipH ?? false,
offsetX: d.offsetX ?? 0,
offsetY: d.offsetY ?? 0,
})); }));
} }

View File

@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:style="{ :style="{
rotate: getDecorationAngle(decoration), rotate: getDecorationAngle(decoration),
scale: getDecorationScale(decoration), scale: getDecorationScale(decoration),
translate: getDecorationOffset(decoration),
}" }"
alt="" alt=""
> >
@ -99,6 +100,12 @@ function getDecorationScale(decoration: Omit<Misskey.entities.UserDetailed['avat
return scaleX === 1 ? undefined : `${scaleX} 1`; return scaleX === 1 ? undefined : `${scaleX} 1`;
} }
function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
const offsetX = decoration.offsetX ?? 0;
const offsetY = decoration.offsetY ?? 0;
return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`;
}
const color = ref<string | undefined>(); const color = ref<string | undefined>();
watch(() => props.user.avatarBlurhash, () => { watch(() => props.user.avatarBlurhash, () => {

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@click="emit('click')" @click="emit('click')"
> >
<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div> <div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div>
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH }]" forceShowDecoration/> <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY }]" forceShowDecoration/>
<i v-if="decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.lock" class="ti ti-lock"></i> <i v-if="decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.lock" class="ti ti-lock"></i>
</div> </div>
</template> </template>
@ -28,6 +28,8 @@ const props = defineProps<{
}; };
angle?: number; angle?: number;
flipH?: boolean; flipH?: boolean;
offsetX?: number;
offsetY?: number;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -23,6 +23,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkRange v-model="angle" continuousUpdate :min="-0.5" :max="0.5" :step="0.025" :textConverter="(v) => `${Math.floor(v * 360)}°`"> <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> <template #label>{{ i18n.ts.angle }}</template>
</MkRange> </MkRange>
<MkRange v-model="offsetX" continuousUpdate :min="-0.25" :max="0.25" :step="0.025" :textConverter="(v) => `${Math.floor(v * 100)}%`">
<template #label>X {{ i18n.ts.position }}</template>
</MkRange>
<MkRange v-model="offsetY" continuousUpdate :min="-0.25" :max="0.25" :step="0.025" :textConverter="(v) => `${Math.floor(v * 100)}%`">
<template #label>Y {{ i18n.ts.position }}</template>
</MkRange>
<MkSwitch v-model="flipH"> <MkSwitch v-model="flipH">
<template #label>{{ i18n.ts.flip }}</template> <template #label>{{ i18n.ts.flip }}</template>
</MkSwitch> </MkSwitch>
@ -64,10 +70,14 @@ const emit = defineEmits<{
(ev: 'attach', payload: { (ev: 'attach', payload: {
angle: number; angle: number;
flipH: boolean; flipH: boolean;
offsetX: number;
offsetY: number;
}): void; }): void;
(ev: 'update', payload: { (ev: 'update', payload: {
angle: number; angle: number;
flipH: boolean; flipH: boolean;
offsetX: number;
offsetY: number;
}): void; }): void;
(ev: 'detach'): void; (ev: 'detach'): void;
}>(); }>();
@ -76,6 +86,8 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0); const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0);
const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0); const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0);
const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false); const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false);
const offsetX = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetX : null) ?? 0);
const offsetY = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetY : null) ?? 0);
const decorationsForPreview = computed(() => { const decorationsForPreview = computed(() => {
const decoration = { const decoration = {
@ -83,6 +95,8 @@ const decorationsForPreview = computed(() => {
url: props.decoration.url, url: props.decoration.url,
angle: angle.value, angle: angle.value,
flipH: flipH.value, flipH: flipH.value,
offsetX: offsetX.value,
offsetY: offsetY.value,
}; };
const decorations = [...$i.avatarDecorations]; const decorations = [...$i.avatarDecorations];
if (props.usingIndex != null) { if (props.usingIndex != null) {
@ -101,6 +115,8 @@ async function update() {
emit('update', { emit('update', {
angle: angle.value, angle: angle.value,
flipH: flipH.value, flipH: flipH.value,
offsetX: offsetX.value,
offsetY: offsetY.value,
}); });
dialog.value.close(); dialog.value.close();
} }
@ -109,6 +125,8 @@ async function attach() {
emit('attach', { emit('attach', {
angle: angle.value, angle: angle.value,
flipH: flipH.value, flipH: flipH.value,
offsetX: offsetX.value,
offsetY: offsetY.value,
}); });
dialog.value.close(); dialog.value.close();
} }

View File

@ -16,6 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)" :decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
:angle="avatarDecoration.angle" :angle="avatarDecoration.angle"
:flipH="avatarDecoration.flipH" :flipH="avatarDecoration.flipH"
:offsetX="avatarDecoration.offsetX"
:offsetY="avatarDecoration.offsetY"
:active="true" :active="true"
@click="openDecoration(avatarDecoration, i)" @click="openDecoration(avatarDecoration, i)"
/> />
@ -66,6 +68,8 @@ function openDecoration(avatarDecoration, index?: number) {
id: avatarDecoration.id, id: avatarDecoration.id,
angle: payload.angle, angle: payload.angle,
flipH: payload.flipH, flipH: payload.flipH,
offsetX: payload.offsetX,
offsetY: payload.offsetY,
}; };
const update = [...$i.avatarDecorations, decoration]; const update = [...$i.avatarDecorations, decoration];
await os.apiWithDialog('i/update', { await os.apiWithDialog('i/update', {
@ -78,6 +82,8 @@ function openDecoration(avatarDecoration, index?: number) {
id: avatarDecoration.id, id: avatarDecoration.id,
angle: payload.angle, angle: payload.angle,
flipH: payload.flipH, flipH: payload.flipH,
offsetX: payload.offsetX,
offsetY: payload.offsetY,
}; };
const update = [...$i.avatarDecorations]; const update = [...$i.avatarDecorations];
update[index] = decoration; update[index] = decoration;