From a86e577127c1d371bed7b6368ab8bfe5bc3ab7bd Mon Sep 17 00:00:00 2001 From: Matt Raymer Date: Tue, 20 May 2025 01:15:47 -0400 Subject: [PATCH] style: improve code formatting and readability - Format Vue template attributes and event handlers for better readability - Reorganize component props and event bindings - Improve error handling and state management in QR scanner - Add proper aria labels and accessibility attributes - Refactor camera state handling in WebInlineQRScanner - Clean up promise handling in WebPlatformService - Standardize string quotes to double quotes - Improve component structure and indentation No functional changes, purely code style and maintainability improvements. --- src/components/ImageMethodDialog.vue | 4 +- src/components/PhotoDialog.vue | 51 +++-- src/services/QRScanner/WebInlineQRScanner.ts | 117 ++++++++---- src/services/QRScanner/types.ts | 18 +- src/services/platforms/WebPlatformService.ts | 187 ++++++++++--------- src/views/AccountViewView.vue | 110 +++++++---- src/views/ContactQRScanShowView.vue | 76 ++++---- 7 files changed, 340 insertions(+), 223 deletions(-) diff --git a/src/components/ImageMethodDialog.vue b/src/components/ImageMethodDialog.vue index 6585b2b5..25c8db93 100644 --- a/src/components/ImageMethodDialog.vue +++ b/src/components/ImageMethodDialog.vue @@ -51,7 +51,9 @@ diff --git a/src/components/PhotoDialog.vue b/src/components/PhotoDialog.vue index 342bf5d4..651ef9ac 100644 --- a/src/components/PhotoDialog.vue +++ b/src/components/PhotoDialog.vue @@ -173,12 +173,12 @@ export default class PhotoDialog extends Vue { * @throws {Error} When settings retrieval fails */ async mounted() { - console.log('PhotoDialog mounted'); + logger.log("PhotoDialog mounted"); try { const settings = await retrieveSettingsForActiveAccount(); this.activeDid = settings.activeDid || ""; this.isRegistered = !!settings.isRegistered; - console.log('isRegistered:', this.isRegistered); + logger.log("isRegistered:", this.isRegistered); } catch (error: unknown) { logger.error("Error retrieving settings from database:", error); this.$notify( @@ -245,7 +245,10 @@ export default class PhotoDialog extends Vue { * Closes the photo dialog and resets state */ close() { - logger.debug("Dialog closing, current showCameraPreview:", this.showCameraPreview); + logger.debug( + "Dialog closing, current showCameraPreview:", + this.showCameraPreview, + ); this.visible = false; this.stopCameraPreview(); const bottomNav = document.querySelector("#QuickNav") as HTMLElement; @@ -291,10 +294,13 @@ export default class PhotoDialog extends Vue { // Set state before requesting camera access this.showCameraPreview = true; logger.debug("showCameraPreview set to:", this.showCameraPreview); - + // Force a re-render await this.$nextTick(); - logger.debug("After nextTick, showCameraPreview is:", this.showCameraPreview); + logger.debug( + "After nextTick, showCameraPreview is:", + this.showCameraPreview, + ); logger.debug("Requesting camera access..."); const stream = await navigator.mediaDevices.getUserMedia({ @@ -302,10 +308,13 @@ export default class PhotoDialog extends Vue { }); logger.debug("Camera access granted, setting up video element"); this.cameraStream = stream; - + // Force another re-render after getting the stream await this.$nextTick(); - logger.debug("After getting stream, showCameraPreview is:", this.showCameraPreview); + logger.debug( + "After getting stream, showCameraPreview is:", + this.showCameraPreview, + ); const videoElement = this.$refs.videoElement as HTMLVideoElement; if (videoElement) { @@ -343,13 +352,19 @@ export default class PhotoDialog extends Vue { * Stops the camera preview and cleans up resources */ stopCameraPreview() { - logger.debug("Stopping camera preview, current showCameraPreview:", this.showCameraPreview); + logger.debug( + "Stopping camera preview, current showCameraPreview:", + this.showCameraPreview, + ); if (this.cameraStream) { this.cameraStream.getTracks().forEach((track) => track.stop()); this.cameraStream = null; } this.showCameraPreview = false; - logger.debug("After stopping, showCameraPreview is:", this.showCameraPreview); + logger.debug( + "After stopping, showCameraPreview is:", + this.showCameraPreview, + ); } /** @@ -366,13 +381,17 @@ export default class PhotoDialog extends Vue { const ctx = canvas.getContext("2d"); ctx?.drawImage(videoElement, 0, 0, canvas.width, canvas.height); - canvas.toBlob((blob) => { - if (blob) { - this.blob = blob; - this.fileName = `photo_${Date.now()}.jpg`; - this.stopCameraPreview(); - } - }, "image/jpeg", 0.95); + canvas.toBlob( + (blob) => { + if (blob) { + this.blob = blob; + this.fileName = `photo_${Date.now()}.jpg`; + this.stopCameraPreview(); + } + }, + "image/jpeg", + 0.95, + ); } catch (error) { logger.error("Error capturing photo:", error); this.$notify( diff --git a/src/services/QRScanner/WebInlineQRScanner.ts b/src/services/QRScanner/WebInlineQRScanner.ts index a038dc22..3f71d953 100644 --- a/src/services/QRScanner/WebInlineQRScanner.ts +++ b/src/services/QRScanner/WebInlineQRScanner.ts @@ -1,4 +1,10 @@ -import { QRScannerService, ScanListener, QRScannerOptions, CameraState, CameraStateListener } from "./types"; +import { + QRScannerService, + ScanListener, + QRScannerOptions, + CameraState, + CameraStateListener, +} from "./types"; import { logger } from "@/utils/logger"; import { EventEmitter } from "events"; import jsQR from "jsqr"; @@ -22,7 +28,7 @@ export class WebInlineQRScanner implements QRScannerService { private readonly FRAME_INTERVAL = 1000 / 15; // ~67ms between frames private lastFrameTime = 0; private cameraStateListeners: Set = new Set(); - private currentState: CameraState = 'off'; + private currentState: CameraState = "off"; private currentStateMessage?: string; constructor(private options?: QRScannerOptions) { @@ -49,15 +55,21 @@ export class WebInlineQRScanner implements QRScannerService { private updateCameraState(state: CameraState, message?: string) { this.currentState = state; this.currentStateMessage = message; - this.cameraStateListeners.forEach(listener => { + this.cameraStateListeners.forEach((listener) => { try { listener.onStateChange(state, message); - logger.info(`[WebInlineQRScanner:${this.id}] Camera state changed to: ${state}`, { - state, - message, - }); + logger.info( + `[WebInlineQRScanner:${this.id}] Camera state changed to: ${state}`, + { + state, + message, + }, + ); } catch (error) { - logger.error(`[WebInlineQRScanner:${this.id}] Error in camera state listener:`, error); + logger.error( + `[WebInlineQRScanner:${this.id}] Error in camera state listener:`, + error, + ); } }); } @@ -74,7 +86,7 @@ export class WebInlineQRScanner implements QRScannerService { async checkPermissions(): Promise { try { - this.updateCameraState('initializing', 'Checking camera permissions...'); + this.updateCameraState("initializing", "Checking camera permissions..."); logger.error( `[WebInlineQRScanner:${this.id}] Checking camera permissions...`, ); @@ -86,7 +98,7 @@ export class WebInlineQRScanner implements QRScannerService { permissions.state, ); const granted = permissions.state === "granted"; - this.updateCameraState(granted ? 'ready' : 'permission_denied'); + this.updateCameraState(granted ? "ready" : "permission_denied"); return granted; } catch (error) { logger.error( @@ -96,14 +108,17 @@ export class WebInlineQRScanner implements QRScannerService { stack: error instanceof Error ? error.stack : undefined, }, ); - this.updateCameraState('error', 'Error checking camera permissions'); + this.updateCameraState("error", "Error checking camera permissions"); return false; } } async requestPermissions(): Promise { try { - this.updateCameraState('initializing', 'Requesting camera permissions...'); + this.updateCameraState( + "initializing", + "Requesting camera permissions...", + ); logger.error( `[WebInlineQRScanner:${this.id}] Requesting camera permissions...`, ); @@ -141,8 +156,8 @@ export class WebInlineQRScanner implements QRScannerService { }, }); - this.updateCameraState('ready', 'Camera permissions granted'); - + this.updateCameraState("ready", "Camera permissions granted"); + // Stop the test stream immediately stream.getTracks().forEach((track) => { logger.error(`[WebInlineQRScanner:${this.id}] Stopping test track:`, { @@ -154,20 +169,35 @@ export class WebInlineQRScanner implements QRScannerService { }); return true; } catch (error) { - const wrappedError = error instanceof Error ? error : new Error(String(error)); - + const wrappedError = + error instanceof Error ? error : new Error(String(error)); + // Update state based on error type - if (wrappedError.name === "NotFoundError" || wrappedError.name === "DevicesNotFoundError") { - this.updateCameraState('not_found', 'No camera found on this device'); + if ( + wrappedError.name === "NotFoundError" || + wrappedError.name === "DevicesNotFoundError" + ) { + this.updateCameraState("not_found", "No camera found on this device"); throw new Error("No camera found on this device"); - } else if (wrappedError.name === "NotAllowedError" || wrappedError.name === "PermissionDeniedError") { - this.updateCameraState('permission_denied', 'Camera access denied'); - throw new Error("Camera access denied. Please grant camera permission and try again"); - } else if (wrappedError.name === "NotReadableError" || wrappedError.name === "TrackStartError") { - this.updateCameraState('in_use', 'Camera is in use by another application'); + } else if ( + wrappedError.name === "NotAllowedError" || + wrappedError.name === "PermissionDeniedError" + ) { + this.updateCameraState("permission_denied", "Camera access denied"); + throw new Error( + "Camera access denied. Please grant camera permission and try again", + ); + } else if ( + wrappedError.name === "NotReadableError" || + wrappedError.name === "TrackStartError" + ) { + this.updateCameraState( + "in_use", + "Camera is in use by another application", + ); throw new Error("Camera is in use by another application"); } else { - this.updateCameraState('error', wrappedError.message); + this.updateCameraState("error", wrappedError.message); throw new Error(`Camera error: ${wrappedError.message}`); } } @@ -406,7 +436,7 @@ export class WebInlineQRScanner implements QRScannerService { this.isScanning = true; this.scanAttempts = 0; this.lastScanTime = Date.now(); - this.updateCameraState('initializing', 'Starting camera...'); + this.updateCameraState("initializing", "Starting camera..."); logger.error(`[WebInlineQRScanner:${this.id}] Starting scan`); // Get camera stream @@ -421,8 +451,8 @@ export class WebInlineQRScanner implements QRScannerService { }, }); - this.updateCameraState('active', 'Camera is active'); - + this.updateCameraState("active", "Camera is active"); + logger.error(`[WebInlineQRScanner:${this.id}] Camera stream obtained:`, { tracks: this.stream.getTracks().map((t) => ({ kind: t.kind, @@ -448,15 +478,22 @@ export class WebInlineQRScanner implements QRScannerService { this.scanQRCode(); } catch (error) { this.isScanning = false; - const wrappedError = error instanceof Error ? error : new Error(String(error)); - + const wrappedError = + error instanceof Error ? error : new Error(String(error)); + // Update state based on error type - if (wrappedError.name === "NotReadableError" || wrappedError.name === "TrackStartError") { - this.updateCameraState('in_use', 'Camera is in use by another application'); + if ( + wrappedError.name === "NotReadableError" || + wrappedError.name === "TrackStartError" + ) { + this.updateCameraState( + "in_use", + "Camera is in use by another application", + ); } else { - this.updateCameraState('error', wrappedError.message); + this.updateCameraState("error", wrappedError.message); } - + if (this.scanListener?.onError) { this.scanListener.onError(wrappedError); } @@ -513,8 +550,11 @@ export class WebInlineQRScanner implements QRScannerService { `[WebInlineQRScanner:${this.id}] Stream stopped event emitted`, ); } catch (error) { - logger.error(`[WebInlineQRScanner:${this.id}] Error stopping scan:`, error); - this.updateCameraState('error', 'Error stopping camera'); + logger.error( + `[WebInlineQRScanner:${this.id}] Error stopping scan:`, + error, + ); + this.updateCameraState("error", "Error stopping camera"); throw error; } finally { this.isScanning = false; @@ -557,8 +597,11 @@ export class WebInlineQRScanner implements QRScannerService { `[WebInlineQRScanner:${this.id}] Cleanup completed successfully`, ); } catch (error) { - logger.error(`[WebInlineQRScanner:${this.id}] Error during cleanup:`, error); - this.updateCameraState('error', 'Error during cleanup'); + logger.error( + `[WebInlineQRScanner:${this.id}] Error during cleanup:`, + error, + ); + this.updateCameraState("error", "Error during cleanup"); throw error; } } diff --git a/src/services/QRScanner/types.ts b/src/services/QRScanner/types.ts index 9d21a69c..65e56918 100644 --- a/src/services/QRScanner/types.ts +++ b/src/services/QRScanner/types.ts @@ -22,15 +22,15 @@ export interface QRScannerOptions { playSound?: boolean; } -export type CameraState = - | 'initializing' // Camera is being initialized - | 'ready' // Camera is ready to use - | 'active' // Camera is actively streaming - | 'in_use' // Camera is in use by another application - | 'permission_denied' // Camera permission was denied - | 'not_found' // No camera found on device - | 'error' // Generic error state - | 'off'; // Camera is off/stopped +export type CameraState = + | "initializing" // Camera is being initialized + | "ready" // Camera is ready to use + | "active" // Camera is actively streaming + | "in_use" // Camera is in use by another application + | "permission_denied" // Camera permission was denied + | "not_found" // No camera found on device + | "error" // Generic error state + | "off"; // Camera is off/stopped export interface CameraStateListener { onStateChange: (state: CameraState, message?: string) => void; diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts index 2643ccd1..9e545aa5 100644 --- a/src/services/platforms/WebPlatformService.ts +++ b/src/services/platforms/WebPlatformService.ts @@ -80,7 +80,9 @@ export class WebPlatformService implements PlatformService { */ async takePicture(): Promise { const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); - const hasGetUserMedia = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia); + const hasGetUserMedia = !!( + navigator.mediaDevices && navigator.mediaDevices.getUserMedia + ); // If on mobile, use file input with capture attribute (existing behavior) if (isMobile || !hasGetUserMedia) { @@ -113,107 +115,120 @@ export class WebPlatformService implements PlatformService { } // Desktop: Use getUserMedia for webcam capture - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { let stream: MediaStream | null = null; let video: HTMLVideoElement | null = null; let captureButton: HTMLButtonElement | null = null; let overlay: HTMLDivElement | null = null; - let cleanup = () => { + const cleanup = () => { if (stream) { stream.getTracks().forEach((track) => track.stop()); } if (video && video.parentNode) video.parentNode.removeChild(video); - if (captureButton && captureButton.parentNode) captureButton.parentNode.removeChild(captureButton); - if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); + if (captureButton && captureButton.parentNode) + captureButton.parentNode.removeChild(captureButton); + if (overlay && overlay.parentNode) + overlay.parentNode.removeChild(overlay); }; - try { - stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" } }); - // Create overlay for video and button - overlay = document.createElement("div"); - overlay.style.position = "fixed"; - overlay.style.top = "0"; - overlay.style.left = "0"; - overlay.style.width = "100vw"; - overlay.style.height = "100vh"; - overlay.style.background = "rgba(0,0,0,0.8)"; - overlay.style.display = "flex"; - overlay.style.flexDirection = "column"; - overlay.style.justifyContent = "center"; - overlay.style.alignItems = "center"; - overlay.style.zIndex = "9999"; - video = document.createElement("video"); - video.autoplay = true; - video.playsInline = true; - video.style.maxWidth = "90vw"; - video.style.maxHeight = "70vh"; - video.srcObject = stream; - overlay.appendChild(video); + // Move async operations inside Promise body + navigator.mediaDevices.getUserMedia({ + video: { facingMode: "user" }, + }) + .then((mediaStream) => { + stream = mediaStream; + // Create overlay for video and button + overlay = document.createElement("div"); + overlay.style.position = "fixed"; + overlay.style.top = "0"; + overlay.style.left = "0"; + overlay.style.width = "100vw"; + overlay.style.height = "100vh"; + overlay.style.background = "rgba(0,0,0,0.8)"; + overlay.style.display = "flex"; + overlay.style.flexDirection = "column"; + overlay.style.justifyContent = "center"; + overlay.style.alignItems = "center"; + overlay.style.zIndex = "9999"; - captureButton = document.createElement("button"); - captureButton.textContent = "Capture Photo"; - captureButton.style.marginTop = "2rem"; - captureButton.style.padding = "1rem 2rem"; - captureButton.style.fontSize = "1.2rem"; - captureButton.style.background = "#2563eb"; - captureButton.style.color = "white"; - captureButton.style.border = "none"; - captureButton.style.borderRadius = "0.5rem"; - captureButton.style.cursor = "pointer"; - overlay.appendChild(captureButton); + video = document.createElement("video"); + video.autoplay = true; + video.playsInline = true; + video.style.maxWidth = "90vw"; + video.style.maxHeight = "70vh"; + video.srcObject = stream; + overlay.appendChild(video); - document.body.appendChild(overlay); + captureButton = document.createElement("button"); + captureButton.textContent = "Capture Photo"; + captureButton.style.marginTop = "2rem"; + captureButton.style.padding = "1rem 2rem"; + captureButton.style.fontSize = "1.2rem"; + captureButton.style.background = "#2563eb"; + captureButton.style.color = "white"; + captureButton.style.border = "none"; + captureButton.style.borderRadius = "0.5rem"; + captureButton.style.cursor = "pointer"; + overlay.appendChild(captureButton); - captureButton.onclick = async () => { - try { - // Create a canvas to capture the frame - const canvas = document.createElement("canvas"); - canvas.width = video!.videoWidth; - canvas.height = video!.videoHeight; - const ctx = canvas.getContext("2d"); - ctx?.drawImage(video!, 0, 0, canvas.width, canvas.height); - canvas.toBlob((blob) => { + document.body.appendChild(overlay); + + captureButton.onclick = () => { + try { + // Create a canvas to capture the frame + const canvas = document.createElement("canvas"); + canvas.width = video!.videoWidth; + canvas.height = video!.videoHeight; + const ctx = canvas.getContext("2d"); + ctx?.drawImage(video!, 0, 0, canvas.width, canvas.height); + canvas.toBlob( + (blob) => { + cleanup(); + if (blob) { + resolve({ + blob, + fileName: `photo_${Date.now()}.jpg`, + }); + } else { + reject(new Error("Failed to capture image from webcam")); + } + }, + "image/jpeg", + 0.95, + ); + } catch (err) { cleanup(); - if (blob) { - resolve({ - blob, - fileName: `photo_${Date.now()}.jpg`, + reject(err); + } + }; + }) + .catch((error) => { + cleanup(); + logger.error("Error accessing webcam:", error); + // Fallback to file input + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + this.processImageFile(file) + .then((blob) => { + resolve({ + blob, + fileName: file.name || "photo.jpg", + }); + }) + .catch((error) => { + logger.error("Error processing fallback image:", error); + reject(new Error("Failed to process fallback image")); }); - } else { - reject(new Error("Failed to capture image from webcam")); - } - }, "image/jpeg", 0.95); - } catch (err) { - cleanup(); - reject(err); - } - }; - } catch (error) { - cleanup(); - logger.error("Error accessing webcam:", error); - // Fallback to file input - const input = document.createElement("input"); - input.type = "file"; - input.accept = "image/*"; - input.onchange = async (e) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) { - try { - const blob = await this.processImageFile(file); - resolve({ - blob, - fileName: file.name || "photo.jpg", - }); - } catch (error) { - logger.error("Error processing fallback image:", error); - reject(new Error("Failed to process fallback image")); + } else { + reject(new Error("No image selected")); } - } else { - reject(new Error("No image selected")); - } - }; - input.click(); - } + }; + input.click(); + }); }); } diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index f4b55383..b97284af 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -3,7 +3,12 @@ -
+

Your Identity @@ -78,31 +83,28 @@ :icon-size="96" :profile-image-url="profileImageUrl" class="inline-block align-text-bottom border border-slate-300 rounded" - @click="showLargeIdenticonUrl = profileImageUrl" role="button" aria-label="View profile image in large size" tabindex="0" + @click="showLargeIdenticonUrl = profileImageUrl" />
- +
@@ -171,14 +176,20 @@ {{ activeDid }} - Copied + Copied
@@ -201,8 +212,8 @@ aria-live="polite" >

- Before you can publicly announce a new project or time - commitment, a friend needs to register you. + Before you can publicly announce a new project or time commitment, a + friend needs to register you.

- +
@@ -297,7 +311,9 @@ class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" aria-labelledby="searchLocationHeading" > -

Location for Searches

+

+ Location for Searches +

@@ -408,9 +424,18 @@ >

Usage Limits

-
+
Checking… - +
{{ limitsMessage }} @@ -468,9 +493,13 @@ class="text-blue-500 text-sm font-semibold mb-3 cursor-pointer" @click="showAdvanced = !showAdvanced" > - {{ showAdvanced ? 'Hide Advanced Settings' : 'Show Advanced Settings' }} + {{ showAdvanced ? "Hide Advanced Settings" : "Show Advanced Settings" }}

-
+

Advanced Settings

Beware: the features here can be confusing and even change data in ways @@ -642,8 +671,14 @@

Claim Server

-
-

Claim Server Configuration

+
+

+ Claim Server Configuration +

-

Error: {{ error }}

- - {{ cameraStateMessage || 'Ready to scan' }} + + {{ cameraStateMessage || "Ready to scan" }}

@@ -246,7 +251,7 @@ export default class ContactQRScanShow extends Vue { initializationStatus = "Initializing camera..."; useQRReader = __USE_QR_READER__; preferredCamera: "user" | "environment" = "environment"; - cameraState: CameraState = 'off'; + cameraState: CameraState = "off"; cameraStateMessage?: string; ETHR_DID_PREFIX = ETHR_DID_PREFIX; @@ -321,36 +326,36 @@ export default class ContactQRScanShow extends Vue { onStateChange: (state, message) => { this.cameraState = state; this.cameraStateMessage = message; - + // Update UI based on camera state switch (state) { - case 'in_use': + case "in_use": this.error = "Camera is in use by another application"; - this.isScanning = false; - this.$notify( - { - group: "alert", - type: "warning", + this.isScanning = false; + this.$notify( + { + group: "alert", + type: "warning", title: "Camera in Use", text: "Please close other applications using the camera and try again", - }, - 5000, - ); + }, + 5000, + ); break; - case 'permission_denied': - this.error = "Camera permission denied"; - this.isScanning = false; - this.$notify( - { - group: "alert", - type: "warning", - title: "Camera Access Required", + case "permission_denied": + this.error = "Camera permission denied"; + this.isScanning = false; + this.$notify( + { + group: "alert", + type: "warning", + title: "Camera Access Required", text: "Please grant camera permission to scan QR codes", - }, - 5000, - ); + }, + 5000, + ); break; - case 'not_found': + case "not_found": this.error = "No camera found"; this.isScanning = false; this.$notify( @@ -363,7 +368,7 @@ export default class ContactQRScanShow extends Vue { 5000, ); break; - case 'error': + case "error": this.error = this.cameraStateMessage || "Camera error"; this.isScanning = false; break; @@ -373,7 +378,8 @@ export default class ContactQRScanShow extends Vue { // Check if scanning is supported first if (!(await scanner.isSupported())) { - this.error = "Camera access requires HTTPS. Please use a secure connection."; + this.error = + "Camera access requires HTTPS. Please use a secure connection."; this.isScanning = false; this.$notify( {