diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-22 13:02:24 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-22 13:02:24 +0900 |
| commit | 4eaa02d25f83eff38cecd6db1724c8626dc3af2e (patch) | |
| tree | beedb76bfeae7a11109f25758f55f097f4c66d24 /packages/frontend/src | |
| parent | Update CHANGELOG.md (diff) | |
| download | sharkey-4eaa02d25f83eff38cecd6db1724c8626dc3af2e.tar.gz sharkey-4eaa02d25f83eff38cecd6db1724c8626dc3af2e.tar.bz2 sharkey-4eaa02d25f83eff38cecd6db1724c8626dc3af2e.zip | |
enhance: improve avatar decoration
Diffstat (limited to 'packages/frontend/src')
4 files changed, 166 insertions, 16 deletions
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 2cfc27ceee..04390c6f0c 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -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; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index de684425a2..e22ed29b7e 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -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, () => { diff --git a/packages/frontend/src/pages/settings/profile.avatar-decoration-dialog.vue b/packages/frontend/src/pages/settings/profile.avatar-decoration-dialog.vue new file mode 100644 index 0000000000..c4bdf4a49b --- /dev/null +++ b/packages/frontend/src/pages/settings/profile.avatar-decoration-dialog.vue @@ -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> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index f3d0c12dce..8d9c3cf730 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -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(() => []); |