This commit is contained in:
syuilo 2025-09-14 21:02:27 +09:00
parent 79abac6f64
commit 268b303e0c
4 changed files with 63 additions and 119 deletions

View File

@ -4,14 +4,18 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, accented ? $style.accented : null]"></div>
<div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"/>
</template>
<script lang="ts" setup>
const props = withDefaults(defineProps<{
accented?: boolean;
revered?: boolean;
height?: number;
}>(), {
accented: false,
revered: false,
height: 200,
});
</script>
@ -27,14 +31,17 @@ const props = withDefaults(defineProps<{
--dot-size: 2px;
--gap-size: 40px;
--offset: calc(var(--gap-size) / 2);
--height: v-bind('props.height + "px"');
height: 200px;
margin-bottom: -200px;
height: var(--height);
background-image: linear-gradient(transparent 60%, transparent 100%), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size)), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size));
background-position: 0 0, 0 0, var(--offset) var(--offset);
background-size: 100% 100%, var(--gap-size) var(--gap-size), var(--gap-size) var(--gap-size);
mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
pointer-events: none;
&.revered {
mask-image: linear-gradient(to top, black 0%, transparent 100%);
}
}
</style>

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
<MkPolkadots v-if="tab === 'home'" accented/>
<MkPolkadots v-if="tab === 'home'" accented :height="200" style="margin-bottom: -200px;"/>
<div class="_spacer" style="--MI_SPACER-w: 700px;">
<XHome v-if="tab === 'home'"/>
<XInvitations v-else-if="tab === 'invitations'"/>

View File

@ -4,39 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div
ref="rootEl" :class="$style.root" :style="{
backgroundColor: bgColor,
}"
>
<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
<div :class="$style.fg">
<div ref="rootEl" :class="$style.root">
<div :class="[$style.content]">
<div
:class="[$style.content, '_spacer']"
:style="{
'--MI_SPACER-w': '512px',
'--MI_SPACER-max': '14px',
ref="qrCodeEl" v-flip :style="{
'cursor': canShare ? 'pointer' : 'default',
}"
>
<div
:class="$style.qrOuter"
:style="{
'cursor': canShare ? 'pointer' : 'default',
}"
@click="share"
>
<div ref="qrCodeEl" v-flip :class="$style.qrInner"></div>
:class="$style.qr" @click="share"
></div>
<div v-flip :class="$style.user">
<MkAvatar :class="$style.avatar" :user="$i" :indicator="false"/>
<div :class="$style.names">
<div :class="$style.name"><MkCondensedLine :minScale="2 / 3"><MkUserName :user="$i" :nowrap="true"/></MkCondensedLine></div>
<div :class="$style.username"><MkCondensedLine :minScale="2 / 3">{{ acct }}</MkCondensedLine></div>
</div>
<div v-flip :class="$style.user">
<MkAvatar :class="$style.avatar" :user="$i" :indicator="false"/>
<div :class="$style.names">
<div :class="$style.name"><MkCondensedLine :minScale="2 / 3"><MkUserName :user="$i" :nowrap="true"/></MkCondensedLine></div>
<div :class="$style.username"><MkCondensedLine :minScale="2 / 3">{{ acct }}</MkCondensedLine></div>
</div>
</div>
<img v-if="deviceMotionPermissionNeeded" v-flip :class="$style.logo" :src="misskeysvg" alt="Misskey Logo" @click="requestDeviceMotion"/>
<img v-else v-flip :class="$style.logo" :src="misskeysvg" alt="Misskey Logo"/>
</div>
<img v-if="deviceMotionPermissionNeeded" v-flip :class="$style.logo" :src="misskeysvg" alt="Misskey Logo" @click="requestDeviceMotion"/>
<img v-else v-flip :class="$style.logo" :src="misskeysvg" alt="Misskey Logo"/>
</div>
</div>
</template>
@ -73,12 +57,7 @@ const canShare = computed(() => navigator.canShare && navigator.canShare(shareDa
const qrCodeEl = ref<HTMLDivElement | null>(null);
const avatarColor = computed(() => tinycolor(
$i.avatarBlurhash ?
extractAvgColorFromBlurhash($i.avatarBlurhash)
: instance.themeColor
?? '#86b300',
));
const avatarColor = computed(() => tinycolor(instance.themeColor ?? '#86b300'));
const avatarHsl = computed(() => avatarColor.value.toHsl());
const bgColor = tinycolor(`hsl(${avatarHsl.value.h}, 60, 46)`).toRgbString();
@ -90,7 +69,7 @@ function share() {
const qrCodeInstance = new QRCodeStyling({
width: 600,
height: 600,
margin: 36,
margin: 42,
type: 'canvas',
data: `${url}/users/${$i.id}`,
image: instance.iconUrl ? getStaticImageUrl(instance.iconUrl) : '/favicon.ico',
@ -107,16 +86,6 @@ const qrCodeInstance = new QRCodeStyling({
},
dotsOptions: {
color: tinycolor(`hsl(${avatarHsl.value.h}, 100, 18)`).toRgbString(),
gradient: {
type: 'linear',
rotation: 1, // radian
colorStops: [
{ offset: 0, color: tinycolor(`hsl(${avatarHsl.value.h}, 100, 25)`).toRgbString() },
{ offset: 0.5, color: tinycolor(`hsl(${avatarHsl.value.h}, 100, 20)`).toRgbString() },
{ offset: 1, color: tinycolor(`hsl(${avatarHsl.value.h}, 100, 6)`).toRgbString() },
],
},
type: 'classy-rounded',
},
backgroundOptions: {
color: tinycolor(`hsl(${avatarHsl.value.h}, 100, 97)`).toRgbString(),
@ -188,19 +157,7 @@ $avatarSize: 58px;
.root {
position: relative;
box-sizing: border-box;
margin-top: calc( -1 * var(--MI-stickyTop) );
margin-bottom: calc( -1 * var(--MI-stickyBottom) );
height: fit-content;
min-height: 100cqh;
}
.fg {
position: sticky;
padding-top: var(--MI-stickyTop);
padding-bottom: var(--MI-stickyBottom);
width: 100%;
height: 100%;
padding-top: 16px;
}
.content {
@ -208,32 +165,34 @@ $avatarSize: 58px;
flex-direction: column;
align-items: center;
margin: 0 auto;
padding-top: $s1;
}
.qrOuter {
display: flex;
.qr {
position: relative;
margin: 0 auto;
width: 100%;
padding: 0 0 $s3;
max-height: max(256px, calc((100cqh - var(--MI-stickyTop, 0px) - var(--MI-stickyBottom, 0px)) * 0.55));
}
.qrInner {
margin: 0;
max-width: 250px;
border-radius: 8px;
overflow: clip;
aspect-ratio: 1;
> svg,
> canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
}
.user {
display: flex;
flex-direction: column;
margin: $s3 auto;
justify-content: center;
align-items: center;
text-align: center;
overflow: visible;
width: fit-content;
max-width: 100%;
@ -242,44 +201,23 @@ $avatarSize: 58px;
.avatar {
width: $avatarSize;
height: $avatarSize;
margin-bottom: 16px;
}
.names {
display: flex;
font-weight: bold;
flex-direction: column;
justify-content: center;
align-items: start;
margin: -2px -2px 0 ($avatarSize * 0.25);
padding-right: 16px;
max-width: 100%;
overflow-x: hidden;
overflow-y: visible;
mask-image: linear-gradient(90deg,#000,#000 calc(100% - 16px),#0000);
}
.name {
display: inline-block;
color: #fff;
font-size: 18px;
line-height: 21px;
width: fit-content;
max-width: 100%;
overflow: visible;
font-weight: bold;
}
.username {
display: inline-block;
color: rgba(255, 255, 255, 0.7);
font-size: 13px;
line-height: 15px;
width: fit-content;
max-width: 100%;
overflow: visible;
}
.logo {
width: 25%;
width: 100px;
margin: $s3 auto 0;
}
</style>

View File

@ -4,39 +4,34 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader v-model:tab="tab" :class="$style.root" :tabs="headerTabs" :swipable="true">
<MkQrShow v-if="tab === 'show'"/>
<MkQrRead v-else-if="tab === 'read'"/>
<MkError v-else-if="error" :error="error"/>
<MkLoading v-else/>
</PageWithHeader>
<div :class="$style.root" class="_pageScrollable">
<div class="_spacer" style="min-height: 100%;">
<MkButton v-if="read" :class="$style.button" rounded @click="read = false"><i class="ti ti-qrcode"></i> {{ i18n.ts._qr.showTabTitle }}</MkButton>
<MkButton v-else :class="$style.button" rounded @click="read = true"><i class="ti ti-scan"></i> {{ i18n.ts._qr.readTabTitle }}</MkButton>
<MkQrRead v-if="read"/>
<MkQrShow v-else/>
</div>
<MkPolkadots v-if="!read" accented revered :height="200" style="position: sticky; bottom: 0; margin-top: -200px;"/>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref, shallowRef } from 'vue';
import MkQrShow from './qr.show.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { ensureSignin } from '@/i';
import MkQrShow from './qr.show.vue';
import MkButton from '@/components/MkButton.vue';
import MkPolkadots from '@/components/MkPolkadots.vue';
// router definitionloginRequired
const $i = ensureSignin();
const tab = ref<'read' | 'show'>('show');
const error = ref<any>(null);
const read = ref(false);
const MkQrRead = defineAsyncComponent(() => import('./qr.read.vue'));
const headerTabs = [{
key: 'show',
title: i18n.ts._qr.showTabTitle,
icon: 'ti ti-qrcode',
}, {
key: 'read',
title: i18n.ts._qr.readTabTitle,
icon: 'ti ti-scan',
}];
definePage(() => ({
title: i18n.ts.qr,
icon: 'ti ti-qrcode',
@ -47,4 +42,8 @@ definePage(() => ({
.root {
height: 100%;
}
.button {
margin: 0 auto 16px auto;
}
</style>