4 Commits

Author SHA1 Message Date
74984caf9e 0.6.10
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-02-28 18:07:22 +00:00
c8b799b078 fix: mirror background canvas for front camera in fullscreen mode 2026-02-28 18:07:10 +00:00
f3a4c1af05 0.6.9
All checks were successful
Deploy to Production / deploy (push) Successful in 14s
2026-02-28 18:05:14 +00:00
616f615d7c fix: improve front camera detection on macOS by checking video track label 2026-02-28 18:04:28 +00:00
3 changed files with 50 additions and 28 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "tools-app", "name": "tools-app",
"version": "0.6.8", "version": "0.6.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tools-app", "name": "tools-app",
"version": "0.6.8", "version": "0.6.10",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"barcode-detector": "^3.1.0", "barcode-detector": "^3.1.0",

View File

@@ -1,7 +1,7 @@
{ {
"name": "tools-app", "name": "tools-app",
"private": true, "private": true,
"version": "0.6.8", "version": "0.6.10",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -8,7 +8,7 @@ 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 isMirrored = ref(false)
const wrapperRef = ref(null) const wrapperRef = ref(null)
const bgCanvas = ref(null) const bgCanvas = ref(null)
let bgRafId = null let bgRafId = null
@@ -65,31 +65,27 @@ const paintDetections = (codes) => {
} }
const scale = drawWidth / vw const scale = drawWidth / vw
const isMirrored = isFront.value // Canvas is mirrored via CSS if isMirrored is true, so no manual coordinate mirroring needed
// Styles // Styles
const styles = getComputedStyle(document.documentElement) const styles = getComputedStyle(document.documentElement)
const accent = styles.getPropertyValue('--primary-accent').trim() || '#00f2fe' const accent = styles.getPropertyValue('--primary-accent').trim() || '#00f2fe'
ctx.lineWidth = 4 ctx.lineWidth = 4
ctx.strokeStyle = accent ctx.strokeStyle = accent
ctx.fillStyle = accent ctx.fillStyle = accent
codes.forEach(code => { codes.forEach(code => {
const points = code.cornerPoints const points = code.cornerPoints
if (!points || points.length < 4) return if (!points || points.length < 4) return
ctx.beginPath() ctx.beginPath()
const transform = (p) => { const transform = (p) => {
let x = p.x * scale + startX let x = p.x * scale + startX
let y = p.y * scale + startY let y = p.y * scale + startY
return { x, y }
if (isMirrored) { }
x = width - x
}
return { x, y }
}
const p0 = transform(points[0]) const p0 = transform(points[0])
ctx.moveTo(p0.x, p0.y) ctx.moveTo(p0.x, p0.y)
@@ -203,6 +199,23 @@ const startScan = async () => {
stream = await navigator.mediaDevices.getUserMedia(constraints) stream = await navigator.mediaDevices.getUserMedia(constraints)
// Detect actual facing mode to mirror front camera correctly
const videoTrack = stream.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) { if (videoRef.value) {
videoRef.value.srcObject = stream videoRef.value.srcObject = stream
// Wait for metadata to play // Wait for metadata to play
@@ -457,6 +470,7 @@ const isUrl = (string) => {
v-if="isFullscreen" v-if="isFullscreen"
ref="bgCanvas" ref="bgCanvas"
class="camera-bg" class="camera-bg"
:class="{ 'is-mirrored': isMirrored }"
></canvas> ></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" />
@@ -464,7 +478,7 @@ const isUrl = (string) => {
<div <div
class="camera-wrapper" class="camera-wrapper"
:class="{ 'clickable': !isFullscreen, 'is-front': isFront }" :class="{ 'clickable': !isFullscreen, 'is-mirrored': isMirrored }"
:style="desktopFullscreenStyle" :style="desktopFullscreenStyle"
ref="wrapperRef" ref="wrapperRef"
@click="!isFullscreen && toggleFullscreen()" @click="!isFullscreen && toggleFullscreen()"
@@ -472,13 +486,13 @@ const isUrl = (string) => {
<video <video
ref="videoRef" ref="videoRef"
class="camera-feed" class="camera-feed"
:class="{ 'is-front': isFront }" :class="{ 'is-mirrored': isMirrored }"
autoplay autoplay
playsinline playsinline
muted muted
></video> ></video>
<canvas ref="overlayCanvas" class="scan-overlay-canvas"></canvas> <canvas ref="overlayCanvas" class="scan-overlay-canvas" :class="{ 'is-mirrored': isMirrored }"></canvas>
<div v-if="error" class="error-overlay"> <div v-if="error" class="error-overlay">
<p>{{ error }}</p> <p>{{ error }}</p>
@@ -625,6 +639,10 @@ const isUrl = (string) => {
z-index: 0; z-index: 0;
} }
.camera-bg.is-mirrored {
transform: scaleX(-1);
}
.camera-feed { .camera-feed {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -632,7 +650,7 @@ const isUrl = (string) => {
display: block; display: block;
} }
.camera-feed.is-front { .camera-feed.is-mirrored {
transform: scaleX(-1); transform: scaleX(-1);
} }
@@ -646,6 +664,10 @@ const isUrl = (string) => {
z-index: 5; z-index: 5;
} }
.scan-overlay-canvas.is-mirrored {
transform: scaleX(-1);
}
/* front mirror canvas removed */ /* front mirror canvas removed */
.error-overlay { .error-overlay {