@ -3,70 +3,30 @@
< div class = "dialog relative" >
< div class = "dialog relative" >
< 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 & hellip ; < / span >
{ { dialogHeading } }
< span v -else -if = " blob " > { {
crop ? "Crop Image" : "Preview Image"
} } < / span >
< span v -else -if = " showCameraPreview " > Upload Image < / span >
< span v-else > Add Photo < / span >
< / h1 >
< / h1 >
< div
< div :class ="closeButtonClasses" @click ="close()" >
class = "text-2xl text-center px-1 py-0.5 leading-none absolute -right-1 top-0"
@ click = "close()"
>
< font -awesome icon = "xmark" class = "w-[1em]" > < / f o n t - a w e s o m e >
< font -awesome icon = "xmark" class = "w-[1em]" > < / f o n t - a w e s o m e >
< / div >
< / div >
< / div >
< / div >
<!-- FEEDBACK : Show if camera preview is not visible after mounting -- >
<!-- FEEDBACK : Show if camera preview is not visible after mounting -- >
< div
< div v-if ="shouldShowCameraFeedback" :class ="cameraFeedbackClasses" >
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 >
< strong > Camera preview not started . < / strong >
< div v-if ="cameraState === 'off'" >
< div > < b > Status : < / b > { { cameraFeedbackMessage } } < / div >
< 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 >
< div class = "mt-4" >
< div class = "mt-4" >
< template v-if ="isRegistered" >
< template v-if ="isRegistered" >
< div v-if ="!blob" >
< div v-if ="!blob" >
< div
< div :class ="sectionDividerClasses" >
class = "border-b border-dashed border-slate-300 text-orange-400 mb-4 font-bold text-sm"
< span :class ="sectionDividerSpanClasses" >
>
< span class = "block w-fit mx-auto -mb-2.5 bg-white px-2" >
Take a photo with your camera
Take a photo with your camera
< / span >
< / span >
< / div >
< / div >
< div
< div v-if ="shouldShowCameraPreview" :class ="cameraPreviewClasses" >
v - if = "showCameraPreview"
class = "camera-preview relative flex bg-black overflow-hidden mb-4"
>
<!-- Diagnostic Panel -- >
<!-- Diagnostic Panel -- >
< div
< div v-if ="showDiagnostics" :class ="diagnosticsPanelClasses" >
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 class = "grid grid-cols-2 gap-2" >
< div >
< div >
< p > < strong > Camera State : < / strong > { { cameraState } } < / p >
< p > < strong > Camera State : < / strong > { { cameraState } } < / p >
@ -108,10 +68,10 @@
<!-- Toggle Diagnostics Button -- >
<!-- Toggle Diagnostics Button -- >
< button
< button
class = "absolute top-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs z-30 "
: class = "diagnosticsToggleClasses "
@ click = "toggleDiagnostics"
@ click = "toggleDiagnostics"
>
>
{ { showDiagnostics ? "Hide Diagnostics" : "Show Diagnostics" } }
{ { diagnosticsToggleText } }
< / button >
< / button >
< div class = "camera-container w-full h-full relative" >
< div class = "camera-container w-full h-full relative" >
< video
< video
@ -121,18 +81,16 @@
playsinline
playsinline
muted
muted
> < / video >
> < / video >
< div
< div :class ="cameraControlsClasses" >
class = "absolute bottom-4 inset-x-0 flex items-center justify-center gap-4"
>
< button
< button
class = "bg-white text-slate-800 p-3 rounded-full text-2xl leading-none "
: class = "cameraControlButtonClasses"
@ click = "capturePhoto"
@ click = "capturePhoto"
>
>
< font -awesome icon = "camera" class = "w-[1em]" / >
< font -awesome icon = "camera" class = "w-[1em]" / >
< / button >
< / button >
< button
< button
v - if = "platformCapabilities.isMobile "
v - if = "shouldShowCameraRotation "
class = "bg-white text-slate-800 p-3 rounded-full text-2xl leading-none "
: class = "cameraControlButtonClasses "
@ click = "rotateCamera"
@ click = "rotateCamera"
>
>
< font -awesome icon = "rotate" class = "w-[1em]" / >
< font -awesome icon = "rotate" class = "w-[1em]" / >
@ -140,24 +98,20 @@
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div
< div :class ="sectionDividerClasses" >
class = "border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-4 font-bold text-sm"
< span :class ="sectionDividerSpanClasses" >
>
< span class = "block w-fit mx-auto -mb-2.5 bg-white px-2" >
OR choose a file from your device
OR choose a file from your device
< / span >
< / span >
< / div >
< / div >
< div class = "mt-4" >
< div class = "mt-4" >
< input
< input
type = "file"
type = "file"
class = "w-full file:text-center file:bg-gradient-to-b file:from-slate-400 file:to-slate-700 file:shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] file:text-white file:px-3 file:py-2 file:rounded-md file:border-none file:cursor-pointer file:me-2 "
: class = "fileInputClasses "
@ change = "uploadImageFile"
@ change = "uploadImageFile"
/ >
/ >
< / div >
< / div >
< div
< div :class ="sectionDividerClasses" >
class = "border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-4 font-bold text-sm"
< span :class ="sectionDividerSpanClasses" >
>
< span class = "block w-fit mx-auto -mb-2.5 bg-white px-2" >
OR paste an image URL
OR paste an image URL
< / span >
< / span >
< / div >
< / div >
@ -165,12 +119,12 @@
< input
< input
v - model = "imageUrl"
v - model = "imageUrl"
type = "text"
type = "text"
class = "block w-full rounded border border-slate-400 px-4 py-2 "
: class = "urlInputClasses "
placeholder = "https://example.com/image.jpg"
placeholder = "https://example.com/image.jpg"
/ >
/ >
< button
< button
v - if = "imageUrl"
v - if = "imageUrl"
class = "bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-2 rounded-md cursor-pointer "
: class = "acceptUrlButtonClasses "
@ click = "acceptUrl"
@ click = "acceptUrl"
>
>
< font -awesome icon = "check" class = "fa-fw" / >
< font -awesome icon = "check" class = "fa-fw" / >
@ -192,38 +146,27 @@
backgroundColor : '#f8f8f8' ,
backgroundColor : '#f8f8f8' ,
margin : 'auto' ,
margin : 'auto' ,
} "
} "
: img = "createBlobURL(blob) "
: img = "blobUrl "
: options = " {
: options = " {
viewMode : 1 ,
viewMode : 1 ,
dragMode : 'crop' ,
dragMode : 'crop' ,
aspectRatio : 1 / 1 ,
aspectRatio : 1 / 1 ,
} "
} "
class = "max-h-[50vh] max-w-[90vw] object-contain "
: class = "cropperClasses "
/ >
/ >
< / div >
< / div >
< div v-else >
< div v-else >
< div class = "flex justify-center" >
< div class = "flex justify-center" >
< img
< img :src ="blobUrl" :class ="imageContainerClasses" / >
: src = "createBlobURL(blob)"
class = "mt-2 rounded max-h-[50vh] max-w-[90vw] object-contain"
/ >
< / div >
< / div >
< / div >
< / div >
< div
< div :class ="buttonGridClasses" >
: class = " [
< button :class ="primaryButtonClasses" @click ="uploadImage" >
'grid gap-2 mt-2' ,
showRetry ? 'grid-cols-2' : 'grid-cols-1' ,
] "
>
< button
class = "bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md"
@ click = "uploadImage"
>
< span > Upload < / span >
< span > Upload < / span >
< / button >
< / button >
< button
< button
v - if = "showRetry"
v - if = "showRetry"
class = "bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md "
: class = "secondaryButtonClasses"
@ click = "retryImage"
@ click = "retryImage"
>
>
< span > Retry < / span >
< span > Retry < / span >
@ -235,7 +178,7 @@
< template v-else >
< template v-else >
< div
< div
id = "noticeBeforeUpload"
id = "noticeBeforeUpload"
class = "bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 "
: class = "registrationNoticeClasses "
role = "alert"
role = "alert"
aria - live = "polite"
aria - live = "polite"
>
>
@ -243,7 +186,7 @@
Before you can upload a photo , a friend needs to register you .
Before you can upload a photo , a friend needs to register you .
< / p >
< / p >
< button
< button
class = "inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md "
: class = "registrationButtonClasses "
@ click = "handleQRCodeClick"
@ click = "handleQRCodeClick"
>
>
Share Your Info
Share Your Info
@ -262,10 +205,20 @@ import { Component, Vue } from "vue-facing-decorator";
import VuePictureCropper , { cropper } from "vue-picture-cropper" ;
import VuePictureCropper , { cropper } from "vue-picture-cropper" ;
import { Capacitor } from "@capacitor/core" ;
import { Capacitor } from "@capacitor/core" ;
import { DEFAULT_IMAGE_API_SERVER , NotificationIface } from "../constants/app" ;
import { DEFAULT_IMAGE_API_SERVER , NotificationIface } from "../constants/app" ;
import {
NOTIFY_IMAGE_DIALOG_SETTINGS_ERROR ,
NOTIFY_IMAGE_DIALOG_RETRIEVAL_ERROR ,
NOTIFY_IMAGE_DIALOG_CAPTURE_ERROR ,
NOTIFY_IMAGE_DIALOG_NO_IMAGE_ERROR ,
createImageDialogCameraErrorMessage ,
createImageDialogUploadErrorMessage ,
IMAGE_DIALOG_TIMEOUT_LONG ,
IMAGE_DIALOG_TIMEOUT_MODAL ,
} from "../constants/notifications" ;
import { accessToken } from "../libs/crypto" ;
import { accessToken } from "../libs/crypto" ;
import { logger } from "../utils/logger" ;
import { logger } from "../utils/logger" ;
import { PlatformServiceFactory } from "../services/PlatformServiceFactory" ;
import { PlatformServiceFactory } from "../services/PlatformServiceFactory" ;
import * as databaseUtil from "../db/databaseUtil" ;
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin ";
import { Prop } from "vue-facing-decorator" ;
import { Prop } from "vue-facing-decorator" ;
import { Router } from "vue-router" ;
import { Router } from "vue-router" ;
@ -273,6 +226,7 @@ const inputImageFileNameRef = ref<Blob>();
@ Component ( {
@ Component ( {
components : { VuePictureCropper } ,
components : { VuePictureCropper } ,
mixins : [ PlatformServiceMixin ] ,
} )
} )
export default class ImageMethodDialog extends Vue {
export default class ImageMethodDialog extends Vue {
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
@ -317,7 +271,7 @@ export default class ImageMethodDialog extends Vue {
/** Current camera facing mode */
/** Current camera facing mode */
private currentFacingMode : "environment" | "user" = "environment" ;
private currentFacingMode : "environment" | "user" = "environment" ;
private platformService = PlatformServiceFactory . getInstance ( ) ;
platformService = PlatformServiceFactory . getInstance ( ) ;
URL = window . URL || window . webkitURL ;
URL = window . URL || window . webkitURL ;
private platformCapabilities = this . platformService . getCapabilities ( ) ;
private platformCapabilities = this . platformService . getCapabilities ( ) ;
@ -350,13 +304,233 @@ export default class ImageMethodDialog extends Vue {
} )
} )
defaultCameraMode ! : string ;
defaultCameraMode ! : string ;
/ * *
* Computed property for dialog heading text
* Determines the appropriate heading based on current state
* /
get dialogHeading ( ) : string {
if ( this . uploading ) return "Uploading Image…" ;
if ( this . blob ) return this . crop ? "Crop Image" : "Preview Image" ;
if ( this . showCameraPreview ) return "Upload Image" ;
return "Add Photo" ;
}
/ * *
* Computed property for camera preview visibility
* Determines if camera preview should be shown
* /
get shouldShowCameraPreview ( ) : boolean {
return this . showCameraPreview && ! this . blob ;
}
/ * *
* Computed property for camera feedback visibility
* Shows feedback when camera preview is not visible after mounting
* /
get shouldShowCameraFeedback ( ) : boolean {
return ! this . showCameraPreview && ! this . blob && this . isRegistered ;
}
/ * *
* Computed property for camera feedback message
* Provides appropriate feedback based on camera state
* /
get cameraFeedbackMessage ( ) : string {
if ( this . cameraState === "off" ) {
if ( this . platformCapabilities . isMobile ) {
return "This mobile browser may not support direct camera access, or the app is treating it as a native app. Try using a desktop browser, or check if your browser supports camera access for web apps." ;
}
return "Your browser supports camera APIs, but the preview did not start. Try refreshing the page or checking browser permissions." ;
}
if ( this . cameraState === "error" ) {
return this . error || this . cameraStateMessage || "Unknown error occurred." ;
}
return this . cameraStateMessage || "Unknown reason." ;
}
/ * *
* Computed property for button grid classes
* Determines grid layout based on retry button visibility
* /
get buttonGridClasses ( ) : string {
return ` grid gap-2 mt-2 ${ this . showRetry ? "grid-cols-2" : "grid-cols-1" } ` ;
}
/ * *
* Computed property for blob URL
* Creates object URL for blob display
* /
get blobUrl ( ) : string {
return this . blob ? this . createBlobURL ( this . blob ) : "" ;
}
/ * *
* Computed property for diagnostics toggle button text
* Determines button text based on diagnostics visibility
* /
get diagnosticsToggleText ( ) : string {
return this . showDiagnostics ? "Hide Diagnostics" : "Show Diagnostics" ;
}
/ * *
* Computed property for camera rotation button visibility
* Shows rotation button only on mobile platforms
* /
get shouldShowCameraRotation ( ) : boolean {
return this . platformCapabilities . isMobile ;
}
/ * *
* Computed property for close button classes
* Provides consistent styling for the close button
* /
get closeButtonClasses ( ) : string {
return "text-2xl text-center px-1 py-0.5 leading-none absolute -right-1 top-0" ;
}
/ * *
* Computed property for camera feedback container classes
* Provides consistent styling for camera feedback messages
* /
get cameraFeedbackClasses ( ) : string {
return "bg-red-100 text-red-700 border border-red-400 rounded px-4 py-3 my-4 text-sm" ;
}
/ * *
* Computed property for section divider classes
* Provides consistent styling for section dividers
* /
get sectionDividerClasses ( ) : string {
return "border-b border-dashed border-slate-300 text-orange-400 mb-4 font-bold text-sm" ;
}
/ * *
* Computed property for section divider span classes
* Provides consistent styling for divider labels
* /
get sectionDividerSpanClasses ( ) : string {
return "block w-fit mx-auto -mb-2.5 bg-white px-2" ;
}
/ * *
* Computed property for camera preview container classes
* Provides consistent styling for camera preview
* /
get cameraPreviewClasses ( ) : string {
return "camera-preview relative flex bg-black overflow-hidden mb-4" ;
}
/ * *
* Computed property for diagnostics panel classes
* Provides consistent styling for diagnostics overlay
* /
get diagnosticsPanelClasses ( ) : string {
return "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]" ;
}
/ * *
* Computed property for diagnostics toggle button classes
* Provides consistent styling for diagnostics toggle
* /
get diagnosticsToggleClasses ( ) : string {
return "absolute top-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs z-30" ;
}
/ * *
* Computed property for camera control button classes
* Provides consistent styling for camera control buttons
* /
get cameraControlButtonClasses ( ) : string {
return "bg-white text-slate-800 p-3 rounded-full text-2xl leading-none" ;
}
/ * *
* Computed property for camera controls container classes
* Provides consistent styling for camera controls
* /
get cameraControlsClasses ( ) : string {
return "absolute bottom-4 inset-x-0 flex items-center justify-center gap-4" ;
}
/ * *
* Computed property for file input classes
* Provides consistent styling for file input with custom button
* /
get fileInputClasses ( ) : string {
return "w-full file:text-center file:bg-gradient-to-b file:from-slate-400 file:to-slate-700 file:shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] file:text-white file:px-3 file:py-2 file:rounded-md file:border-none file:cursor-pointer file:me-2" ;
}
/ * *
* Computed property for URL input classes
* Provides consistent styling for URL input field
* /
get urlInputClasses ( ) : string {
return "block w-full rounded border border-slate-400 px-4 py-2" ;
}
/ * *
* Computed property for accept URL button classes
* Provides consistent styling for accept URL button
* /
get acceptUrlButtonClasses ( ) : string {
return "bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-2 rounded-md cursor-pointer" ;
}
/ * *
* Computed property for image container classes
* Provides consistent styling for image display
* /
get imageContainerClasses ( ) : string {
return "mt-2 rounded max-h-[50vh] max-w-[90vw] object-contain" ;
}
/ * *
* Computed property for cropper container classes
* Provides consistent styling for image cropper
* /
get cropperClasses ( ) : string {
return "max-h-[50vh] max-w-[90vw] object-contain" ;
}
/ * *
* Computed property for primary button classes
* Provides consistent styling for primary action buttons
* /
get primaryButtonClasses ( ) : string {
return "bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md" ;
}
/ * *
* Computed property for secondary button classes
* Provides consistent styling for secondary action buttons
* /
get secondaryButtonClasses ( ) : string {
return "bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md" ;
}
/ * *
* Computed property for registration notice classes
* Provides consistent styling for registration notice
* /
get registrationNoticeClasses ( ) : string {
return "bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3" ;
}
/ * *
* Computed property for registration button classes
* Provides consistent styling for registration button
* /
get registrationButtonClasses ( ) : string {
return "inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md" ;
}
/ * *
/ * *
* Lifecycle hook : Initializes component and retrieves user settings
* Lifecycle hook : Initializes component and retrieves user settings
* @ throws { Error } When settings retrieval fails
* @ throws { Error } When settings retrieval fails
* /
* /
async mounted ( ) {
async mounted ( ) {
try {
try {
const settings = await databaseUtil . retrieveSettingsForActiveAccount ( ) ;
const settings = await this . $accountSettings ( ) ;
this . activeDid = settings . activeDid || "" ;
this . activeDid = settings . activeDid || "" ;
} catch ( error : unknown ) {
} catch ( error : unknown ) {
logger . error ( "Error retrieving settings from database:" , error ) ;
logger . error ( "Error retrieving settings from database:" , error ) ;
@ -368,9 +542,9 @@ export default class ImageMethodDialog extends Vue {
text :
text :
error instanceof Error
error instanceof Error
? error . message
? error . message
: "There was an error retrieving your settings." ,
: NOTIFY_IMAGE_DIALOG_SETTINGS_ERROR . message ,
} ,
} ,
- 1 ,
IMAGE_DIALOG_TIMEOUT_MODAL ,
) ;
) ;
}
}
}
}
@ -435,9 +609,9 @@ export default class ImageMethodDialog extends Vue {
group : "alert" ,
group : "alert" ,
type : "danger" ,
type : "danger" ,
title : "Error" ,
title : "Error" ,
text : "There was an error retrieving that image." ,
text : NOTIFY_IMAGE_DIALOG_RETRIEVAL_ERROR . message ,
} ,
} ,
5000 ,
IMAGE_DIALOG_TIMEOUT_LONG ,
) ;
) ;
}
}
} else {
} else {
@ -510,22 +684,9 @@ export default class ImageMethodDialog extends Vue {
}
}
} catch ( error ) {
} catch ( error ) {
logger . error ( "Error starting camera preview:" , error ) ;
logger . error ( "Error starting camera preview:" , error ) ;
let errorMessage =
const errorMessage = createImageDialogCameraErrorMessage (
error instanceof Error ? error . message : "Failed to access camera" ;
error instanceof Error ? error : new Error ( "Unknown camera error" ) ,
if (
) ;
error instanceof Error &&
( 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 instanceof Error &&
( 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 ;
@ -537,7 +698,7 @@ export default class ImageMethodDialog extends Vue {
title : "Error" ,
title : "Error" ,
text : errorMessage ,
text : errorMessage ,
} ,
} ,
5000 ,
IMAGE_DIALOG_TIMEOUT_LONG ,
) ;
) ;
}
}
}
}
@ -583,9 +744,9 @@ export default class ImageMethodDialog extends Vue {
group : "alert" ,
group : "alert" ,
type : "danger" ,
type : "danger" ,
title : "Error" ,
title : "Error" ,
text : "Failed to capture photo. Please try again." ,
text : NOTIFY_IMAGE_DIALOG_CAPTURE_ERROR . message ,
} ,
} ,
5000 ,
IMAGE_DIALOG_TIMEOUT_LONG ,
) ;
) ;
}
}
}
}
@ -634,9 +795,9 @@ export default class ImageMethodDialog extends Vue {
group : "alert" ,
group : "alert" ,
type : "danger" ,
type : "danger" ,
title : "Error" ,
title : "Error" ,
text : "There was an error finding the picture. Please try again." ,
text : NOTIFY_IMAGE_DIALOG_NO_IMAGE_ERROR . message ,
} ,
} ,
5000 ,
IMAGE_DIALOG_TIMEOUT_LONG ,
) ;
) ;
this . uploading = false ;
this . uploading = false ;
this . close ( ) ;
this . close ( ) ;
@ -663,25 +824,7 @@ export default class ImageMethodDialog extends Vue {
this . close ( ) ;
this . close ( ) ;
this . imageCallback ( response . data . url as string ) ;
this . imageCallback ( response . data . url as string ) ;
} catch ( error : unknown ) {
} catch ( error : unknown ) {
let errorMessage = "There was an error saving the picture." ;
const errorMessage = createImageDialogUploadErrorMessage ( error ) ;
if ( axios . isAxiosError ( error ) ) {
const status = error . response ? . status ;
const data = error . response ? . data ;
if ( status === 401 ) {
errorMessage = "Authentication failed. Please try logging in again." ;
} else if ( status === 413 ) {
errorMessage = "Image file is too large. Please try a smaller image." ;
} else if ( status === 415 ) {
errorMessage =
"Unsupported image format. Please try a different image." ;
} else if ( status && status >= 500 ) {
errorMessage = "Server error. Please try again later." ;
} else if ( data ? . message ) {
errorMessage = data . message ;
}
}
this . $notify (
this . $notify (
{
{
@ -690,7 +833,7 @@ export default class ImageMethodDialog extends Vue {
title : "Error" ,
title : "Error" ,
text : errorMessage ,
text : errorMessage ,
} ,
} ,
5000 ,
IMAGE_DIALOG_TIMEOUT_LONG ,
) ;
) ;
this . uploading = false ;
this . uploading = false ;
this . blob = undefined ;
this . blob = undefined ;