qr.show.vue

This commit is contained in:
tamaina 2025-08-25 08:09:08 +09:00
parent 926cd832c6
commit 9c9e6c9ca9
4 changed files with 63 additions and 19 deletions

8
locales/index.d.ts vendored
View File

@ -12561,6 +12561,14 @@ export interface Locale extends ILocale {
*
*/
"readTabTitle": string;
/**
* {name} {acct}
*/
"shareTitle": ParameterizedString<"name" | "acct">;
/**
* Fediverseで私をフォローしてください
*/
"shareText": string;
};
}
declare const locales: {

View File

@ -3364,3 +3364,5 @@ qr: 二次元コード
_qr:
showTabTitle: "表示"
readTabTitle: "読み取る"
shareTitle: "{name} {acct}"
shareText: "Fediverseで私をフォローしてください"

View File

@ -17,5 +17,6 @@ const $i = ensureSignin();
<style lang="scss" module>
.root {
padding: 16px;
}
</style>

View File

@ -5,35 +5,51 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root" :style="{ backgroundColor: bgColor }">
<div :class="$style.content" class="_spacer" :style="{ '--MI_SPACER-w': '512px' }">
<div :class="$style.qrOuter">
<div v-flip-on-device-orientation ref="qrCodeEl" :class="$style.qrInner"></div>
</div>
<div :class="$style.user">
<MkAvatar v-flip-on-device-orientation :class="$style.avatar" :user="$i" :indicator="false"/>
<div :class="$style.names">
<div v-flip-on-device-orientation :class="$style.username"><MkCondensedLine :minScale="2 / 3">@{{ $i.username }}@{{ host }}</MkCondensedLine></div>
<div v-flip-on-device-orientation :class="$style.name"><MkCondensedLine :minScale="2 / 3">{{ userName($i) }}</MkCondensedLine></div>
<MkAnimBg style="position: absolute; top: 0;" :scale="1.5" />
<div :class="$style.fg">
<div
:class="$style.content" class="_spacer"
@click="share"
:style="{
'--MI_SPACER-w': '512px',
'cursor': canShare ? 'pointer' : 'default',
}"
>
<div :class="$style.qrOuter">
<div v-flip-on-device-orientation ref="qrCodeEl" :class="$style.qrInner"></div>
</div>
<div :class="$style.user">
<MkAvatar v-flip-on-device-orientation :class="$style.avatar" :user="$i" :indicator="false"/>
<div :class="$style.names">
<div v-flip-on-device-orientation :class="$style.username"><MkCondensedLine :minScale="2 / 3">{{ acct }}</MkCondensedLine></div>
<div v-flip-on-device-orientation :class="$style.name"><MkCondensedLine :minScale="2 / 3">{{ userName($i) }}</MkCondensedLine></div>
</div>
</div>
<img v-flip-on-device-orientation :class="$style.logo" :src="misskeysvg" alt="Misskey Logo"/>
</div>
<img v-flip-on-device-orientation :class="$style.logo" :src="misskeysvg" alt="Misskey Logo"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash';
import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
import tinycolor from 'tinycolor2';
import QRCodeStyling from 'qr-code-styling';
import { computed, ref, watch } from 'vue';
import { computed, ref, watch, onMounted } from 'vue';
import { host } from '@@/js/config.js';
import { instance } from '@/instance';
import { ensureSignin } from '@/i';
import { userPage, userName } from '@/filters/user';
import { instance } from '@/instance.js';
import { ensureSignin } from '@/i.js';
import { userPage, userName } from '@/filters/user.js';
import misskeysvg from '/client-assets/misskey.svg';
import MkAnimBg from '@/components/MkAnimBg.vue';
const $i = ensureSignin();
const acct = computed(() => `@${$i.username}@${host}`);
const url = computed(() => userPage($i, undefined, true));
const canShare = computed(() => navigator.canShare && navigator.canShare({ url: url.value }));
const qrCodeEl = ref<HTMLDivElement | null>(null);
const avatarColor = computed(() => tinycolor(
@ -45,13 +61,21 @@ const avatarColor = computed(() => tinycolor(
const avatarHsl = computed(() => avatarColor.value.toHsl());
const bgColor = tinycolor(`hsl(${avatarHsl.value.h}, 60, 46)`).toRgbString();
watch([qrCodeEl, avatarHsl], () => {
function share() {
return navigator.share?.({
title: i18n.tsx._qr.shareTitle({ name: userName($i), acct: acct.value }),
text: i18n.tsx._qr.shareText({ name: userName($i), acct: acct.value }),
url: url.value,
});
}
watch([qrCodeEl, avatarHsl, url], () => {
const qrCodeInstance = new QRCodeStyling({
width: 512,
height: 512,
margin: 40,
type: 'canvas',
data: userPage($i, undefined, true),
data: url.value,
image: instance.iconUrl ?? '/favicon.ico',
qrOptions: {
typeNumber: 0,
@ -91,11 +115,19 @@ watch([qrCodeEl, avatarHsl], () => {
<style lang="scss" module>
.root {
position: relative;
margin-top: calc( -1 * var(--MI-stickyTop) );
padding-top: var(--MI-stickyTop);
height: calc( 100dvh - var(--MI-stickyTop) - var(--MI-stickyBottom) );
}
.fg {
position: absolute;
top: var(--MI-stickyTop);
width: 100%;
height: 100%;
}
.content {
display: flex;
flex-direction: column;
@ -114,8 +146,9 @@ watch([qrCodeEl, avatarHsl], () => {
> svg,
> canvas {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
object-fit: contain;
}
}