chore: prepare release; reintroduce front camera CSS mirror, stabilize QR Scanner
This commit is contained in:
@@ -9,12 +9,60 @@ const scannedCodes = ref([])
|
|||||||
const hasMultipleCameras = ref(false)
|
const hasMultipleCameras = ref(false)
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false)
|
||||||
const videoAspect = ref(1)
|
const videoAspect = ref(1)
|
||||||
|
const isFront = computed(() => facingMode.value === 'user')
|
||||||
|
const wrapperRef = ref(null)
|
||||||
|
let frontMirrorObserver = null
|
||||||
|
const bgCanvas = ref(null)
|
||||||
|
let bgRafId = null
|
||||||
const updateVideoAspect = () => {
|
const updateVideoAspect = () => {
|
||||||
const videoEl = document.querySelector('.camera-wrapper video')
|
const videoEl = document.querySelector('.camera-wrapper video')
|
||||||
if (videoEl && videoEl.videoWidth && videoEl.videoHeight) {
|
if (videoEl && videoEl.videoWidth && videoEl.videoHeight) {
|
||||||
videoAspect.value = videoEl.videoWidth / videoEl.videoHeight
|
videoAspect.value = videoEl.videoWidth / videoEl.videoHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const startBackgroundLoop = () => {
|
||||||
|
const draw = () => {
|
||||||
|
const videoEl = document.querySelector('.camera-wrapper video')
|
||||||
|
const canvas = bgCanvas.value
|
||||||
|
if (!videoEl || !canvas) {
|
||||||
|
bgRafId = requestAnimationFrame(draw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const vw = videoEl.videoWidth || 0
|
||||||
|
const vh = videoEl.videoHeight || 0
|
||||||
|
if (!vw || !vh) {
|
||||||
|
bgRafId = requestAnimationFrame(draw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const cw = Math.floor(window.innerWidth)
|
||||||
|
const ch = Math.floor(window.innerHeight * 0.5)
|
||||||
|
if (canvas.width !== cw || canvas.height !== ch) {
|
||||||
|
canvas.width = cw
|
||||||
|
canvas.height = ch
|
||||||
|
}
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (ctx) {
|
||||||
|
// cover horizontally: scale by width, crop top/bottom
|
||||||
|
const scale = cw / vw
|
||||||
|
const srcH = ch / scale
|
||||||
|
const sx = 0
|
||||||
|
const sy = Math.max(0, (vh - srcH) / 2)
|
||||||
|
ctx.clearRect(0, 0, cw, ch)
|
||||||
|
ctx.drawImage(videoEl, sx, sy, vw, srcH, 0, 0, cw, ch)
|
||||||
|
}
|
||||||
|
bgRafId = requestAnimationFrame(draw)
|
||||||
|
}
|
||||||
|
if (bgRafId) cancelAnimationFrame(bgRafId)
|
||||||
|
bgRafId = requestAnimationFrame(draw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// front mirror canvas removed to restore stable behavior
|
||||||
|
const stopBackgroundLoop = () => {
|
||||||
|
if (bgRafId) {
|
||||||
|
cancelAnimationFrame(bgRafId)
|
||||||
|
bgRafId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
const desktopFullscreenStyle = computed(() => {
|
const desktopFullscreenStyle = computed(() => {
|
||||||
if (!isFullscreen.value) return {}
|
if (!isFullscreen.value) return {}
|
||||||
const isDesktop = window.innerWidth >= 768
|
const isDesktop = window.innerWidth >= 768
|
||||||
@@ -95,8 +143,26 @@ const onDetect = (detectedCodes) => {
|
|||||||
const onCameraOn = async (capabilities) => {
|
const onCameraOn = async (capabilities) => {
|
||||||
// Camera is ready
|
// Camera is ready
|
||||||
setTimeout(updateVideoAspect, 100)
|
setTimeout(updateVideoAspect, 100)
|
||||||
|
setTimeout(startBackgroundLoop, 150)
|
||||||
|
// Flip is handled via global CSS; no JS flips needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ensureFrontMirror = () => {
|
||||||
|
// No-op: mirror is applied via CSS selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFrontMirrorObserver = () => {
|
||||||
|
// No-op: mirror is applied via CSS selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopFrontMirrorObserver = () => {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isFront, () => {
|
||||||
|
// CSS-based; nothing to do
|
||||||
|
})
|
||||||
|
|
||||||
const paintDetections = (detectedCodes, ctx) => {
|
const paintDetections = (detectedCodes, ctx) => {
|
||||||
try {
|
try {
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
|
||||||
@@ -171,10 +237,20 @@ onMounted(() => {
|
|||||||
checkCameras()
|
checkCameras()
|
||||||
loadHistory()
|
loadHistory()
|
||||||
window.addEventListener('resize', updateVideoAspect)
|
window.addEventListener('resize', updateVideoAspect)
|
||||||
|
window.addEventListener('resize', startBackgroundLoop)
|
||||||
|
watch(isFullscreen, (fs) => {
|
||||||
|
if (fs) {
|
||||||
|
startBackgroundLoop()
|
||||||
|
} else {
|
||||||
|
stopBackgroundLoop()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', updateVideoAspect)
|
window.removeEventListener('resize', updateVideoAspect)
|
||||||
|
window.removeEventListener('resize', startBackgroundLoop)
|
||||||
|
stopBackgroundLoop()
|
||||||
})
|
})
|
||||||
|
|
||||||
const switchCamera = (event) => {
|
const switchCamera = (event) => {
|
||||||
@@ -244,11 +320,22 @@ const isUrl = (string) => {
|
|||||||
|
|
||||||
<Teleport to="body" :disabled="!isFullscreen">
|
<Teleport to="body" :disabled="!isFullscreen">
|
||||||
<div class="scanner-content" :class="{ 'is-fullscreen': isFullscreen }">
|
<div class="scanner-content" :class="{ 'is-fullscreen': isFullscreen }">
|
||||||
|
<canvas
|
||||||
|
v-if="isFullscreen"
|
||||||
|
ref="bgCanvas"
|
||||||
|
class="camera-bg"
|
||||||
|
></canvas>
|
||||||
<button v-if="isFullscreen" class="close-fullscreen-btn" @click="toggleFullscreen">
|
<button v-if="isFullscreen" class="close-fullscreen-btn" @click="toggleFullscreen">
|
||||||
<X size="24" />
|
<X size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="camera-wrapper" :class="{ 'clickable': !isFullscreen }" :style="desktopFullscreenStyle" @click="!isFullscreen && toggleFullscreen()">
|
<div
|
||||||
|
class="camera-wrapper"
|
||||||
|
:class="{ 'clickable': !isFullscreen, 'is-front': isFront }"
|
||||||
|
:style="desktopFullscreenStyle"
|
||||||
|
ref="wrapperRef"
|
||||||
|
@click="!isFullscreen && toggleFullscreen()"
|
||||||
|
>
|
||||||
<QrcodeStream
|
<QrcodeStream
|
||||||
:constraints="{ facingMode }"
|
:constraints="{ facingMode }"
|
||||||
@detect="onDetect"
|
@detect="onDetect"
|
||||||
@@ -384,9 +471,22 @@ const isUrl = (string) => {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 50vh;
|
||||||
|
filter: blur(16px) saturate(110%);
|
||||||
|
opacity: 0.9;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* front mirror canvas removed */
|
||||||
|
|
||||||
.error-overlay {
|
.error-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -614,6 +714,17 @@ const isUrl = (string) => {
|
|||||||
inset: 0 !important;
|
inset: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Front camera mirror (CSS-only) */
|
||||||
|
.camera-wrapper.is-front :deep(video) {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
.camera-wrapper.is-front :deep(#qrcode-stream-pause-frame),
|
||||||
|
.camera-wrapper.is-front :deep(#qrcode-stream-overlay) {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
:deep(.scanner-content.is-fullscreen .camera-wrapper video) {
|
:deep(.scanner-content.is-fullscreen .camera-wrapper video) {
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Removed global front camera mirror to restore stability */
|
||||||
|
|
||||||
/* --- Shared styles for all tools (moved from tools.css) --- */
|
/* --- Shared styles for all tools (moved from tools.css) --- */
|
||||||
|
|
||||||
.tool-container {
|
.tool-container {
|
||||||
|
|||||||
Reference in New Issue
Block a user