fix: normalize font-weight for Length/Count labels in Passwords; refactor QR scanner composables; style fixes

This commit is contained in:
2026-03-03 14:29:58 +00:00
parent 6f95dce55a
commit 011db26ec4
6 changed files with 287 additions and 264 deletions

View File

@@ -1,110 +1,110 @@
import { ref, watch, onUnmounted } from 'vue'
export function useCamera(videoRef) {
const stream = ref(null)
const facingMode = ref('environment')
const hasMultipleCameras = ref(false)
const isMirrored = ref(false)
const error = ref('')
const stream = ref(null)
const facingMode = ref('environment')
const hasMultipleCameras = ref(false)
const isMirrored = ref(false)
const error = ref('')
const checkCameras = async () => {
try {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
return
}
const devices = await navigator.mediaDevices.enumerateDevices()
const cameras = devices.filter(d => d.kind === 'videoinput')
hasMultipleCameras.value = cameras.length > 1
} catch (e) {
console.error('Error checking cameras:', e)
const checkCameras = async () => {
try {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
return
}
const devices = await navigator.mediaDevices.enumerateDevices()
const cameras = devices.filter(d => d.kind === 'videoinput')
hasMultipleCameras.value = cameras.length > 1
} catch (e) {
console.error('Error checking cameras:', e)
}
}
const stopCamera = () => {
if (stream.value) {
stream.value.getTracks().forEach(t => t.stop())
stream.value = null
}
}
const startCamera = async () => {
stopCamera()
error.value = ''
try {
const constraints = {
video: {
facingMode: facingMode.value,
width: { ideal: 1280 },
height: { ideal: 720 }
}
}
}
const stopCamera = () => {
if (stream.value) {
stream.value.getTracks().forEach(t => t.stop())
stream.value = null
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints)
stream.value = mediaStream
// Detect actual facing mode to mirror front camera correctly
const videoTrack = mediaStream.getVideoTracks()[0]
if (videoTrack) {
const settings = videoTrack.getSettings()
if (settings.facingMode) {
isMirrored.value = settings.facingMode === 'user'
} else {
// Fallback: check label for desktop cameras or assume requested mode
const label = videoTrack.label ? videoTrack.label.toLowerCase() : ''
if (label.includes('front') || label.includes('facetime') || label.includes('macbook')) {
isMirrored.value = true
} else {
isMirrored.value = facingMode.value === 'user'
}
}
}
if (videoRef.value) {
videoRef.value.srcObject = mediaStream
return new Promise((resolve) => {
videoRef.value.onloadedmetadata = () => {
videoRef.value.play().catch(e => console.error('Play error', e))
resolve()
}
})
}
} catch (err) {
if (err.name === 'NotAllowedError') {
error.value = 'Camera permission denied'
} else if (err.name === 'NotFoundError') {
error.value = 'No camera found'
} else {
error.value = `Camera error: ${err.name}`
}
throw err // Let caller know it failed
}
}
const startCamera = async () => {
stopCamera()
error.value = ''
const switchCamera = () => {
facingMode.value = facingMode.value === 'environment' ? 'user' : 'environment'
}
try {
const constraints = {
video: {
facingMode: facingMode.value,
width: { ideal: 1280 },
height: { ideal: 720 }
}
}
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints)
stream.value = mediaStream
// Detect actual facing mode to mirror front camera correctly
const videoTrack = mediaStream.getVideoTracks()[0]
if (videoTrack) {
const settings = videoTrack.getSettings()
if (settings.facingMode) {
isMirrored.value = settings.facingMode === 'user'
} else {
// Fallback: check label for desktop cameras or assume requested mode
const label = videoTrack.label ? videoTrack.label.toLowerCase() : ''
if (label.includes('front') || label.includes('facetime') || label.includes('macbook')) {
isMirrored.value = true
} else {
isMirrored.value = facingMode.value === 'user'
}
}
}
if (videoRef.value) {
videoRef.value.srcObject = mediaStream
return new Promise((resolve) => {
videoRef.value.onloadedmetadata = () => {
videoRef.value.play().catch(e => console.error('Play error', e))
resolve()
}
})
}
} catch (err) {
if (err.name === 'NotAllowedError') {
error.value = 'Camera permission denied'
} else if (err.name === 'NotFoundError') {
error.value = 'No camera found'
} else {
error.value = `Camera error: ${err.name}`
}
throw err // Let caller know it failed
}
watch(facingMode, () => {
if (stream.value) {
// Re-start if already running
startCamera().catch(() => { })
}
})
const switchCamera = () => {
facingMode.value = facingMode.value === 'environment' ? 'user' : 'environment'
}
onUnmounted(() => {
stopCamera()
})
watch(facingMode, () => {
if (stream.value) {
// Re-start if already running
startCamera().catch(() => { })
}
})
onUnmounted(() => {
stopCamera()
})
return {
stream,
facingMode,
hasMultipleCameras,
isMirrored,
error,
checkCameras,
startCamera,
stopCamera,
switchCamera
}
return {
stream,
facingMode,
hasMultipleCameras,
isMirrored,
error,
checkCameras,
startCamera,
stopCamera,
switchCamera
}
}