This commit is contained in:
tamaina 2025-08-26 03:09:53 +09:00
parent 6c67d79f0c
commit f04d6f7a76
4 changed files with 195 additions and 8 deletions

View File

@ -64,6 +64,7 @@
"photoswipe": "5.4.4",
"punycode.js": "2.3.1",
"qr-code-styling": "1.9.2",
"qr-scanner": "1.4.2",
"rollup": "4.48.0",
"sanitize-html": "2.17.0",
"sass": "1.90.0",

View File

@ -4,19 +4,195 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
TODO
<div ref="rootEl" :class="$style.root" :style="{
'--MI-QrReadScrollHeight': scrollContainer ? `${scrollHeight}px` : `calc( 100dvh - var(--MI-minBottomSpacing) )`,
'--MI-QrReadViewHeight': 'calc(var(--MI-QrReadScrollHeight) - var(--MI-stickyTop, 0px) - var(--MI-stickyBottom, 0px))',
'--MI-QrReadVideoHeight': 'min(calc(var(--MI-QrReadViewHeight) * 0.3), 512px)',
}">
<video ref="videoEl" :class="$style.video" autoplay muted playsinline></video>
<div class="_spacer" :style="{
'--MI-stickyTop': 'calc(var(--MI-stickyTop, 0px) + var(--MI-QrReadVideoHeight, 0px))',
}">
<div :class="$style.listBig">
<MkUserInfo v-for="user in users.slice(0, 5)" :key="user.id" :user="user"/>
</div>
<div :class="$style.listMini">
<MkA v-for="user in users.slice(5)" :key="user.id" :to="userPage(user)">
<MkUserCardMini :user="user" />
</MkA>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ensureSignin } from '@/i';
import QrScanner from 'qr-scanner';
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import * as misskey from 'misskey-js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import MkUserInfo from '@/components/MkUserInfo.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { userPage } from '@/filters/user.js';
import { getScrollContainer } from '@@/js/scroll.js';
const $i = ensureSignin();
const scrollContainer = shallowRef<HTMLElement | null>(null);
const rootEl = ref<HTMLDivElement | null>(null);
const scrollHeight = ref(window.innerHeight);
const scannerInstance = shallowRef<QrScanner | null>(null);
const uris = ref<string[]>([]);
const usersSource = new Map<string, misskey.entities.UserDetailed | null>();
const users = ref<(misskey.entities.UserDetailed)[]>([]);
let timer = ref<ReturnType<typeof setTimeout> | null>(null);
function updateUsers() {
users.value = uris.value.map(uri => usersSource.get(uri)).filter(u => u) as misskey.entities.UserDetailed[];
}
watch([uris, timer], () => {
if (timer.value) {
console.log('Skip updateUsers because of timer');
return;
}
updateUsers();
timer.value = setTimeout(() => {
console.log('Update users after 3 seconds');
timer.value = null;
}, 3000);
});
async function processResult(result: QrScanner.ScanResult) {
if (!result) return;
const uri = result.data.trim();
try {
new URL(uri);
} catch {
return;
}
if (uris.value[0] !== uri) {
//
uris.value = [uri, ...uris.value.slice(0, 29).filter(u => u !== uri)];
}
if (usersSource.has(uri)) return;
// Start fetching user info
usersSource.set(uri, null);
await misskeyApi('ap/show', { uri })
.then(data => {
if (data.type === 'User') {
usersSource.set(uri, data.object);
updateUsers();
}
});
}
const alertLock = ref(false);
onMounted(() => {
const videoEl = document.querySelector('video');
if (!videoEl) {
os.alert({
type: 'error',
text: i18n.ts.somethingHappened,
});
return;
}
scannerInstance.value = new QrScanner(
videoEl,
processResult,
{
onDecodeError(err) {
if (err === 'No QR code found') return;
if (alertLock.value) return;
alertLock.value = true;
os.alert({
type: 'error',
text: err.toString(),
}).finally(() => {
alertLock.value = false;
});
},
},
);
scannerInstance.value.start();
});
onUnmounted(() => {
if (timer.value) {
clearTimeout(timer.value);
timer.value = null;
}
scannerInstance.value?.destroy();
});
//#region scroll height
function checkScrollHeight() {
scrollHeight.value = scrollContainer.value ? scrollContainer.value.clientHeight : window.innerHeight;
}
let ro: ResizeObserver | undefined;
onMounted(() => {
scrollContainer.value = getScrollContainer(rootEl.value);
checkScrollHeight();
if (scrollContainer.value) {
ro = new ResizeObserver(checkScrollHeight);
ro.observe(scrollContainer.value);
}
});
onUnmounted(() => {
ro?.disconnect();
});
//#endregion
</script>
<style lang="scss" module>
.root {
padding: 16px;
position: relative;
}
.video {
position: sticky;
background: var(--MI_THEME-bg);
background-size: 16px 16px;
width: 100%;
height: var(--MI-QrReadVideoHeight);
object-fit: contain;
}
html[data-color-scheme=dark] .video {
--c: rgb(255 255 255 / 2%);
background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
}
html[data-color-scheme=light] .video {
--c: rgb(0 0 0 / 2%);
background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
}
.listBig {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(405px, 1fr));
grid-gap: var(--MI-margin);
}
.listMini {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
grid-gap: 12px;
}
</style>

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="rootEl" :class="$style.root" :style="{
backgroundColor: bgColor,
'--MI-QrShowMinHeight': scrollContainer ? `${scrollHeight}px` : `calc( 100dvh - var(--MI-minBottomSpacing) )`,
'--MI-QrShowScrollHeight': scrollContainer ? `${scrollHeight}px` : `calc( 100dvh - var(--MI-minBottomSpacing, 0px) )`,
}">
<MkAnimBg style="position: absolute; top: 0;" :scale="1.5" />
<div :class="$style.fg">
@ -214,7 +214,7 @@ $avatarSize: 58px;
margin-top: calc( -1 * var(--MI-stickyTop) );
margin-bottom: calc( -1 * var(--MI-stickyBottom) );
height: fit-content;
min-height: var(--MI-QrShowMinHeight);
min-height: var(--MI-QrShowScrollHeight);
}
.fg {
@ -237,7 +237,7 @@ $avatarSize: 58px;
display: flex;
width: 100%;
padding: 0 0 $s3;
max-height: max(256px, calc((var(--MI-QrShowMinHeight) - var(--MI-stickyTop) - var(--MI-stickyBottom)) * 0.55));
max-height: max(256px, calc((var(--MI-QrShowScrollHeight) - var(--MI-stickyTop, 0px) - var(--MI-stickyBottom, 0px)) * 0.55));
}
.qrInner {

View File

@ -847,6 +847,9 @@ importers:
qr-code-styling:
specifier: 1.9.2
version: 1.9.2
qr-scanner:
specifier: 1.4.2
version: 1.4.2
rollup:
specifier: 4.48.0
version: 4.48.0
@ -9515,6 +9518,9 @@ packages:
resolution: {integrity: sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==}
engines: {node: '>=18.18.0'}
qr-scanner@1.4.2:
resolution: {integrity: sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==}
qrcode-generator@1.5.2:
resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==}
@ -21094,6 +21100,10 @@ snapshots:
dependencies:
qrcode-generator: 1.5.2
qr-scanner@1.4.2:
dependencies:
'@types/offscreencanvas': 2019.7.0
qrcode-generator@1.5.2: {}
qrcode@1.5.4: