@ -1,6 +1,37 @@
< template >
< div v-if ="visible" class="dialog-overlay z-[60]" >
< div class = "dialog relative" >
<!-- Diagnostic Panel -- >
< div
v - if = "showDiagnostics"
class = "absolute top-0 left-0 right-0 bg-black/80 text-white text-xs p-2 z-20 overflow-auto max-h-[30vh]"
>
< div class = "grid grid-cols-2 gap-2" >
< div >
< p > < strong > Camera State : < / strong > { { cameraState } } < / p >
< p > < strong > State Message : < / strong > { { cameraStateMessage || 'None' } } < / p >
< p > < strong > Error : < / strong > { { error || 'None' } } < / p >
< p > < strong > Preview Active : < / strong > { { showCameraPreview ? 'Yes' : 'No' } } < / p >
< p > < strong > Stream Active : < / strong > { { ! ! cameraStream ? 'Yes' : 'No' } } < / p >
< / div >
< div >
< p > < strong > Browser : < / strong > { { userAgent } } < / p >
< p > < strong > HTTPS : < / strong > { { isSecureContext ? 'Yes' : 'No' } } < / p >
< p > < strong > MediaDevices : < / strong > { { hasMediaDevices ? 'Yes' : 'No' } } < / p >
< p > < strong > GetUserMedia : < / strong > { { hasGetUserMedia ? 'Yes' : 'No' } } < / p >
< p > < strong > Platform : < / strong > { { platformCapabilities . isMobile ? 'Mobile' : 'Desktop' } } < / p >
< / div >
< / div >
< / div >
<!-- Toggle Diagnostics Button -- >
< button
@ click = "toggleDiagnostics"
class = "absolute top-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs z-30"
>
{ { showDiagnostics ? 'Hide Diagnostics' : 'Show Diagnostics' } }
< / button >
< div class = "text-lg text-center font-bold relative" >
< h1 id = "ViewHeading" class = "text-center font-bold" >
< span v-if ="uploading" > Uploading Image & hellip ; < / span >
@ -16,6 +47,28 @@
< / div >
< / div >
<!-- FEEDBACK : Show if camera preview is not visible after mounting -- >
< div v-if ="!showCameraPreview && !blob && isRegistered" class="bg-red-100 text-red-700 border border-red-400 rounded px-4 py-3 my-4 text-sm" >
< strong > Camera preview not started . < / strong >
< div v-if ="cameraState === 'off'" >
< span v-if ="platformCapabilities.isMobile" >
< b > Note : < / b > This mobile browser may not support direct camera access , or the app is treating it as a native app . < br >
< b > Tip : < / b > Try using a desktop browser , or check if your browser supports camera access for web apps . < br >
< b > Developer : < / b > The platform detection logic may be skipping camera preview for mobile browsers . < br >
< b > Action : < / b > Review < code > platformCapabilities . isMobile < / code > and ensure web browsers on mobile are not treated as native apps .
< / span >
< span v-else >
< b > Tip : < / b > Your browser supports camera APIs , but the preview did not start . Try refreshing the page or checking browser permissions .
< / span >
< / div >
< div v -else -if = " cameraState = = = ' error ' " >
< b > Error : < / b > { { error || cameraStateMessage } }
< / div >
< div v-else >
< b > Status : < / b > { { cameraStateMessage || 'Unknown reason.' } }
< / div >
< / div >
< div class = "mt-4" >
< template v-if ="isRegistered" >
< div v-if ="!blob" >
@ -227,6 +280,16 @@ export default class ImageMethodDialog extends Vue {
private platformCapabilities = this . platformService . getCapabilities ( ) ;
/ / A d d d i a g n o s t i c p r o p e r t i e s
showDiagnostics = false ;
userAgent = navigator . userAgent ;
isSecureContext = window . isSecureContext ;
hasMediaDevices = ! ! navigator . mediaDevices ;
hasGetUserMedia = ! ! ( navigator . mediaDevices && navigator . mediaDevices . getUserMedia ) ;
cameraState : 'off' | 'initializing' | 'ready' | 'active' | 'error' | 'permission_denied' | 'not_found' | 'in_use' = 'off' ;
cameraStateMessage ? : string ;
error : string | null = null ;
/ * *
* Lifecycle hook : Initializes component and retrieves user settings
* @ throws { Error } When settings retrieval fails
@ -251,6 +314,14 @@ export default class ImageMethodDialog extends Vue {
- 1 ,
) ;
}
/ / T r y t o s t a r t c a m e r a p r e v i e w a u t o m a t i c a l l y i f n o t o n m o b i l e
if ( ! this . platformCapabilities . isNativeApp ) {
this . startCameraPreview ( ) ;
} else {
logger . warn ( "Camera preview not started: running in native app context." ) ;
this . cameraState = 'off' ;
this . cameraStateMessage = 'Camera preview not started due to native app context.' ;
}
}
/ * *
@ -267,7 +338,7 @@ export default class ImageMethodDialog extends Vue {
this . visible = true ;
/ / S t a r t c a m e r a p r e v i e w i m m e d i a t e l y i f n o t o n m o b i l e
if ( ! this . platformCapabilities . isMobile ) {
if ( ! this . platformCapabilities . isNativeApp ) {
this . startCameraPreview ( ) ;
}
}
@ -339,14 +410,21 @@ export default class ImageMethodDialog extends Vue {
logger . debug ( "Current showCameraPreview state:" , this . showCameraPreview ) ;
logger . debug ( "Platform capabilities:" , this . platformCapabilities ) ;
if ( this . platformCapabilities . isMobile ) {
if ( this . platformCapabilities . isNativeApp ) {
logger . debug ( "Using platform service for mobile device" ) ;
this . cameraState = 'initializing' ;
this . cameraStateMessage = 'Using platform camera service...' ;
try {
const result = await this . platformService . takePicture ( ) ;
this . blob = result . blob ;
this . fileName = result . fileName ;
this . cameraState = 'ready' ;
this . cameraStateMessage = 'Photo captured successfully' ;
} catch ( error ) {
logger . error ( "Error taking picture:" , error ) ;
this . cameraState = 'error' ;
this . cameraStateMessage = error instanceof Error ? error . message : 'Failed to take picture' ;
this . error = error instanceof Error ? error . message : 'Failed to take picture' ;
this . $notify (
{
group : "alert" ,
@ -362,13 +440,18 @@ export default class ImageMethodDialog extends Vue {
logger . debug ( "Starting camera preview for desktop browser" ) ;
try {
this . cameraState = 'initializing' ;
this . cameraStateMessage = 'Requesting camera access...' ;
this . showCameraPreview = true ;
await this . $nextTick ( ) ;
const stream = await navigator . mediaDevices . getUserMedia ( {
video : { facingMode : "environment" } ,
} ) ;
logger . debug ( "Camera access granted" ) ;
this . cameraStream = stream ;
this . cameraState = 'active' ;
this . cameraStateMessage = 'Camera is active' ;
await this . $nextTick ( ) ;
@ -385,6 +468,10 @@ export default class ImageMethodDialog extends Vue {
}
} catch ( error ) {
logger . error ( "Error starting camera preview:" , error ) ;
const errorMessage = error instanceof Error ? error . message : 'Failed to access camera' ;
this . cameraState = 'error' ;
this . cameraStateMessage = errorMessage ;
this . error = errorMessage ;
this . $notify (
{
group : "alert" ,
@ -404,6 +491,9 @@ export default class ImageMethodDialog extends Vue {
this . cameraStream = null ;
}
this . showCameraPreview = false ;
this . cameraState = 'off' ;
this . cameraStateMessage = 'Camera stopped' ;
this . error = null ;
}
async capturePhoto ( ) {
@ -449,7 +539,7 @@ export default class ImageMethodDialog extends Vue {
async retryImage ( ) {
this . blob = undefined ;
if ( ! this . platformCapabilities . isMobile ) {
if ( ! this . platformCapabilities . isNativeApp ) {
await this . startCameraPreview ( ) ;
}
}
@ -533,6 +623,11 @@ export default class ImageMethodDialog extends Vue {
this . blob = undefined ;
}
}
/ / A d d t o g g l e m e t h o d
toggleDiagnostics ( ) {
this . showDiagnostics = ! this . showDiagnostics ;
}
}
< / script >
@ -562,4 +657,11 @@ export default class ImageMethodDialog extends Vue {
display : flex ;
flex - direction : column ;
}
/* Add styles for diagnostic panel */
. diagnostic - panel {
font - family : monospace ;
white - space : pre - wrap ;
word - break : break - all ;
}
< / style >