enhance: improve avatar decoration
This commit is contained in:
@@ -34,6 +34,7 @@ const props = withDefaults(defineProps<{
|
||||
textConverter?: (value: number) => string,
|
||||
showTicks?: boolean;
|
||||
easing?: boolean;
|
||||
continuousUpdate?: boolean;
|
||||
}>(), {
|
||||
step: 1,
|
||||
textConverter: (v) => v.toString(),
|
||||
@@ -123,6 +124,10 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
|
||||
const pointerX = ev.touches && ev.touches.length > 0 ? ev.touches[0].clientX : ev.clientX;
|
||||
const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth / 2));
|
||||
rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth)));
|
||||
|
||||
if (props.continuousUpdate) {
|
||||
emit('update:modelValue', finalValue.value);
|
||||
}
|
||||
};
|
||||
|
||||
let beforeValue = finalValue.value;
|
||||
|
@@ -23,7 +23,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img v-if="decoration || user.avatarDecorations.length > 0" :class="[$style.decoration]" :src="decoration ?? user.avatarDecorations[0].url" alt="">
|
||||
<img
|
||||
v-if="decoration || user.avatarDecorations.length > 0"
|
||||
:class="[$style.decoration]"
|
||||
:src="decoration?.url ?? user.avatarDecorations[0].url"
|
||||
:style="{
|
||||
rotate: getDecorationAngle(),
|
||||
scale: getDecorationScale(),
|
||||
}"
|
||||
alt=""
|
||||
>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -48,12 +57,18 @@ const props = withDefaults(defineProps<{
|
||||
link?: boolean;
|
||||
preview?: boolean;
|
||||
indicator?: boolean;
|
||||
decoration?: string;
|
||||
decoration?: {
|
||||
url: string;
|
||||
angle?: number;
|
||||
flipH?: boolean;
|
||||
flipV?: boolean;
|
||||
};
|
||||
}>(), {
|
||||
target: null,
|
||||
link: false,
|
||||
preview: false,
|
||||
indicator: false,
|
||||
decoration: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -73,6 +88,30 @@ 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
||||
}
|
||||
|
||||
let color = $ref<string | undefined>();
|
||||
|
||||
watch(() => props.user.avatarBlurhash, () => {
|
||||
|
@@ -0,0 +1,114 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="400"
|
||||
:height="450"
|
||||
@close="cancel"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>{{ i18n.ts.avatarDecorations }}</template>
|
||||
|
||||
<div>
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div style="text-align: center;">
|
||||
<div :class="$style.name">{{ decoration.name }}</div>
|
||||
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decoration="{ url: decoration.url, angle, flipH }"/>
|
||||
</div>
|
||||
<div class="_gaps_s">
|
||||
<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>
|
||||
<MkSwitch v-model="flipH">
|
||||
<template #label>{{ i18n.ts.flip }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
|
||||
<div :class="$style.footer" class="_buttonsCenter">
|
||||
<MkButton v-if="using" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton>
|
||||
<MkButton v-if="using" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton>
|
||||
<MkButton v-else primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef, ref, computed } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { $i } from '@/account.js';
|
||||
|
||||
const props = defineProps<{
|
||||
decoration: {
|
||||
id: string;
|
||||
url: string;
|
||||
}
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const using = computed(() => $i.avatarDecorations.some(x => x.id === props.decoration.id));
|
||||
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);
|
||||
|
||||
function cancel() {
|
||||
dialog.value.close();
|
||||
}
|
||||
|
||||
async function attach() {
|
||||
const decoration = {
|
||||
id: props.decoration.id,
|
||||
angle: angle.value,
|
||||
flipH: flipH.value,
|
||||
};
|
||||
await os.apiWithDialog('i/update', {
|
||||
avatarDecorations: [decoration],
|
||||
});
|
||||
$i.avatarDecorations = [decoration];
|
||||
|
||||
dialog.value.close();
|
||||
}
|
||||
|
||||
async function detach() {
|
||||
await os.apiWithDialog('i/update', {
|
||||
avatarDecorations: [],
|
||||
});
|
||||
$i.avatarDecorations = [];
|
||||
|
||||
dialog.value.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.name {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
</style>
|
@@ -92,10 +92,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
|
||||
@click="toggleDecoration(avatarDecoration)"
|
||||
@click="openDecoration(avatarDecoration)"
|
||||
>
|
||||
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="2 / 3">{{ avatarDecoration.name }}</MkCondensedLine></div>
|
||||
<MkAvatar style="width: 64px; height: 64px;" :user="$i" :decoration="avatarDecoration.url"/>
|
||||
<MkAvatar style="width: 64px; height: 64px;" :user="$i" :decoration="{ url: avatarDecoration.url }"/>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
@@ -266,18 +266,10 @@ function changeBanner(ev) {
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDecoration(avatarDecoration) {
|
||||
if ($i.avatarDecorations.some(x => x.id === avatarDecoration.id)) {
|
||||
os.apiWithDialog('i/update', {
|
||||
avatarDecorations: [],
|
||||
});
|
||||
$i.avatarDecorations = [];
|
||||
} else {
|
||||
os.apiWithDialog('i/update', {
|
||||
avatarDecorations: [avatarDecoration.id],
|
||||
});
|
||||
$i.avatarDecorations.push(avatarDecoration);
|
||||
}
|
||||
function openDecoration(avatarDecoration) {
|
||||
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration-dialog.vue')), {
|
||||
decoration: avatarDecoration,
|
||||
}, {}, 'closed');
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
Reference in New Issue
Block a user