forked from trent_larson/crowd-funder-for-time-pwa
Only request camera permissions on user gesture in ImageMethodDialog
- Removed automatic call to startCameraPreview() from mounted() lifecycle hook - Camera preview (and permission prompt) now only starts in open(), triggered by user action - Prevents unnecessary permission prompts on page load and improves UX
This commit is contained in:
@@ -1,6 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="dialog-overlay z-[60]">
|
<div v-if="visible" class="dialog-overlay z-[60]">
|
||||||
<div class="dialog relative">
|
<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">
|
<div class="text-lg text-center font-bold relative">
|
||||||
<h1 id="ViewHeading" class="text-center font-bold">
|
<h1 id="ViewHeading" class="text-center font-bold">
|
||||||
<span v-if="uploading">Uploading Image…</span>
|
<span v-if="uploading">Uploading Image…</span>
|
||||||
@@ -52,39 +83,10 @@
|
|||||||
v-if="showCameraPreview"
|
v-if="showCameraPreview"
|
||||||
class="camera-preview relative flex bg-black overflow-hidden mb-4"
|
class="camera-preview relative flex bg-black overflow-hidden mb-4"
|
||||||
>
|
>
|
||||||
<!-- Diagnostic Panel -->
|
|
||||||
<div
|
|
||||||
v-if="showDiagnostics"
|
|
||||||
class="absolute top-0 left-0 right-0 bg-black/80 text-white text-xs p-2 pt-8 z-20 overflow-auto max-h-[50vh]"
|
|
||||||
>
|
|
||||||
<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="camera-container w-full h-full relative">
|
<div class="camera-container w-full h-full relative">
|
||||||
<video
|
<video
|
||||||
ref="videoElement"
|
ref="videoElement"
|
||||||
class="camera-video w-full h-full portrait:max-h-[40vh] object-cover"
|
class="camera-video w-full h-full object-cover"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
muted
|
muted
|
||||||
@@ -312,14 +314,6 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Try to start camera preview automatically if not on mobile
|
|
||||||
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.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -466,7 +460,12 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error starting camera preview:", error);
|
logger.error("Error starting camera preview:", error);
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Failed to access camera';
|
let errorMessage = error instanceof Error ? error.message : 'Failed to access camera';
|
||||||
|
if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
|
||||||
|
errorMessage = 'Camera is in use by another application. Please close any other apps or browser tabs using the camera and try again.';
|
||||||
|
} else if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
|
||||||
|
errorMessage = 'Camera access was denied. Please allow camera access in your browser settings.';
|
||||||
|
}
|
||||||
this.cameraState = 'error';
|
this.cameraState = 'error';
|
||||||
this.cameraStateMessage = errorMessage;
|
this.cameraStateMessage = errorMessage;
|
||||||
this.error = errorMessage;
|
this.error = errorMessage;
|
||||||
@@ -475,7 +474,7 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "Failed to access camera. Please try again.",
|
text: errorMessage,
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user